Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

white_noise() PSD contributions can be negative #116

Open
topolarity opened this issue Mar 25, 2024 · 20 comments
Open

white_noise() PSD contributions can be negative #116

topolarity opened this issue Mar 25, 2024 · 20 comments

Comments

@topolarity
Copy link

topolarity commented Mar 25, 2024

First of all, thanks for a creating such a great tool.

I've been exploring some of the noise support recently and ran into an issue. It seems that the sign of the PSD contribution of white_noise() can be negative, depending on the direction of the terminals/flow.

Here's an example that I believe shows the problem:

`include "disciplines.vams"
`include "constants.vams"

module resistor_osdi(a, b);
    inout a, b;
    electrical a, b;

    parameter real R=1 from (0.0:inf);

    real G;
    analog begin
        G = 1 / R;
        I(a, b) <+ G * V(a, b);
        I(b, a) <+ white_noise(G * 4 * `P_K * $temperature, "thermal");
    end
endmodule

Compile this with openvaf resistor.va -o resistor.osdi, and then perform .noise analysis in a simple circuit:

* Resistor (OSDI) test

.model R_1_Ohm resistor_osdi R = 1

NR1 out 0 R_1_Ohm
NR2 src out R_1_Ohm
V1 0 src 1 AC = 1

.control
pre_osdi ./resistor.osdi
set sqrnoise
noise v(out) v1 dec 5 1k 10k
setplot noise1
print onoise_spectrum
.endc

.END

results in:

Note: can't find the initialization file spinit.
******
** ngspice-42 : Circuit level simulation program
** Compiled with KLU Direct Linear Solver
** The U. C. Berkeley CAD Group
** Copyright 1985-1994, Regents of the University of California.
** Copyright 2001-2023, The ngspice team.
** Please get your ngspice manual from https://ngspice.sourceforge.io/docs.html
** Please file your bug-reports at http://ngspice.sourceforge.net/bugrep.html
** Creation Date: Sun Feb  4 03:12:21 UTC 2024
******

Note: No compatibility mode selected!

Added device: URC

Circuit: * resistor (osdi) test

Doing analysis at TEMP = 27.000000 and TNOM = 27.000000

Using SPARSE 1.3 as Direct Linear Solver

No. of Data Rows : 6

No. of Data Rows : 1
                             * resistor (osdi) test
                             Noise Spectral Density Curves - (V^2 or A^2)/Hz  Mon Mar 25 10:54:18  2024
--------------------------------------------------------------------------------
Index   frequency       onoise_spectrum
--------------------------------------------------------------------------------
0       1.000000e+03    -8.28804e-21
1       1.584893e+03    -8.28804e-21
2       2.511886e+03    -8.28804e-21
3       3.981072e+03    -8.28804e-21
4       6.309573e+03    -8.28804e-21
5       1.000000e+04    -8.28804e-21

Tested with:

  • latest master of OpenVAF (locally-built)
  • latest master of ngspice (with --enable-osdi, locally-built)

The result looks correct if the white_noise() contribution flows into I(a, b) instead of I(b, a).

@dwarning
Copy link

Can confirm the problem.
In a first guess I think we can fix it in the ngspice-osdi interface.
@metroid120 : Your opinion?
BTW, I am not sure about constraints of contribution directions. Found nothing in LRM.

@gjcoram
Copy link

gjcoram commented Mar 26, 2024

That's weird - I don't see a minus sign in the code you provided. I wonder if openvaf changes the sign of the contribution if it sees the terminals have been swapped. Eg
I(a,b) <+ value1;
I(b,a) <+ value2;
gets rewritten as
I(a,b) <+ value1;
I(a,b) <+ -value2;
perhaps in order to simplify the matrix pointers.

Note that standard ac noise analysis uses the magnitude of the transfer function from the nodes where the noise is injected to the circuit noise output node(s), and thus should be insensitive to whether it's I(a,b) or I(b,a).
The sign of the noise power can be used in PNoise or HBnoise analysis -- please see https://ieeexplore.ieee.org/document/8957705 -- but the simulator must handle the minus sign.

@gjcoram
Copy link

gjcoram commented Mar 26, 2024

I'm not set up to run these experiments myself today, but I'd be interested to know what you get if you change the second line of the example to
I(a, b) <+ -white_noise(G * 4 * P_K * $temperature, "thermal"); or I(a, b) <+ white_noise(-G * 4 * P_K * $temperature, "thermal");
Does openvaf put in an absolute value in either or both of those cases?

@topolarity
Copy link
Author

The sign appears to behave the same for the explicit unary minus or for the flow reversal:

I(a, b) <+  white_noise(G * 4 * `P_K * $temperature, "thermal"); // PSD > 0
I(a, b) <+ -white_noise(G * 4 * `P_K * $temperature, "thermal"); // PSD < 0 (wrong)
I(b, a) <+  white_noise(G * 4 * `P_K * $temperature, "thermal"); // PSD < 0 (wrong)
I(b, a) <+ -white_noise(G * 4 * `P_K * $temperature, "thermal"); // PSD > 0

@dwarning
Copy link

There is a preliminary fix in ngspice git branch pre-master-43.

@topolarity
Copy link
Author

Thanks for the fix! That does seem to help the case above, but I think there may still be a bug here for other transformations:

I(a, b) <+ 2 * white_noise(G * 4 * `P_K * $temperature, "thermal");

I would have expected this to quadruple the noise power (3.3152176e-20), but instead the power merely doubles (1.657609e-20)

@dwarning
Copy link

I am not sure, but I would expect factor of 2 for psd and a factor of sqrt(2) for voltage spectral density. An that is what I got.

@topolarity
Copy link
Author

topolarity commented Mar 28, 2024

I believe that disagrees with the results from this circuit:

* Resistor (OSDI) test

.model R_1_Ohm resistor_osdi R = 1

NR1 src 0 R_1_Ohm
V1 src 0 1 AC = 1
H1 out 0 V1 1.0

.control
pre_osdi ./resistor.osdi
set sqrnoise
noise v(out) v1 dec 5 1k 10k
setplot noise1
print onoise_spectrum
.endc

.END

I would expect the onoise_spectrum to be the same (a) if I set H1 to a transconductance of 2.0 Ω and leave the model as above, or (b) if I leave it at a transconductance of 1.0 Ω and multiply all of the model currents by 2x.

However, the results disagree by a factor of 2x.

@gjcoram
Copy link

gjcoram commented Apr 1, 2024

onoise_spectrum in ngspice is reported in V^2/Hz.
The onoise_spectrum is computed as the PSD of the noise source times the magnitude-squared of the transfer function (H times its complex conjugate).
I think I agree with topolarity: the factor of '2' in front of the call to white_noise() is effectively doubling the transfer function from the white_noise function to the output, so that the magnitude-squared gets multiplied by four, and thus the onoise_spectrum should be multiplied by four.

@dwarning
Copy link

dwarning commented Apr 1, 2024

Speaking about the original request fro topolarity, because last reply is different:

ngspice in default gives onoise_spectrum in V/sqrt(Hz), the voltage spectral density. By 'set sqrnoise' you get power spectral density in V**2/Hz.

    I(a, b) <+ white_noise(G * 4 * `P_K * $temperature, "thermal");

//spectral density (unset sqrnoise)
//onoise ngspice: 9.103869e-11 V/sqrt(Hz)
//power spectral density (PSD set sqrnoise)
//onoise ngspice: 8.288044e-21 V^2/Hz Xyce: 8.28804375e-2 V^2/Hz

// I(a, b) <+ 2 * white_noise(G * 4 * `P_K * $temperature, "thermal");
//spectral density (unset sqrnoise)
//onoise ngspice: 1.287482e-10 V/sqr(Hz) (= *sqrt(2))
//power spectral density (PSD set sqrnoise)
//onoise ngspice: 1.657609e-20 V^2/Hz Xyce: error in compilation

// I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal"); // I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal");
//spectral density (unset sqrnoise)
//onoise ngspice: 1.287482e-10 V/sqr(Hz) (= *sqr(2))
//power spectral density (PSD set sqrnoise)
//onoise ngspice: 1.657609e-20 V^2/Hz Xyce: 1.65760875e-20 V^2/Hz

If we have a factor of 2 what is the same as two contributions to same branch we can assume it is a parallel circuit of two uncorrelated noise sources. The resulting equivalent noise current rising with sqrt(N).
This was the trick we have used by adapting our low source impedance magnetic heads to our cheap transistor pre-amps in the 70' last century.
I'm curious about to see results of the commercial simulators.

@gjcoram
Copy link

gjcoram commented Apr 1, 2024

I was unaware of sqrnoise; I see that topolarity did set that option.

@gjcoram
Copy link

gjcoram commented Apr 1, 2024

If we have a factor of 2 what is the same as two contributions to same branch we can assume
it is a parallel circuit of two uncorrelated noise sources.

I am not sure this is a valid way to understand the case. In particular, it seems that the factor of two in fact makes two correlated noise sources.

@gjcoram
Copy link

gjcoram commented Apr 1, 2024

I have now checked two commercial simulators using
I(b, a) <+ ns1 * white_noise(ns2 * G * 4 * P_K * $temperature, "thermal");

I get different answers for (ns1=1, ns2=2) than for (ns1=2, ns2=1).

@gjcoram
Copy link

gjcoram commented Apr 2, 2024

To clarify my previous post: when ns1=ns2=1, the noise for R=1 at T=27 C is ~ 1.66e-20 V^2/Hz
For ns1=1, ns2=2, the noise is 3.32e-20 V^2/Hz, which is twice the baseline
For ns1=2, ns2=1, the noise is 6.63e-20 V^2/Hz, which is four times the baseline
Both commercial simulators agree with each other (and with an internal proprietary simulator).

ngspice agrees for the baseline and the first case, but the second case matches the first, that is, I get twice the baseline instead of four times.

@dwarning
Copy link

dwarning commented Apr 3, 2024

@gjcoram :
Thank you for clarification.
Last question, how this is handled by the reference simulators? Same as factor ns1 = 2?
I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal");
I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal");
Then I should litter my Motchenbacher, p 246.

@tcaduser
Copy link

tcaduser commented Apr 3, 2024

@gjcoram : Thank you for clarification. Last question, how this is handled by the reference simulators? Same as factor ns1 = 2? I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal"); I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal"); Then I should litter my Motchenbacher, p 246.

My reading of this page from the LRM is that noise processes with the same name would be summed together as uncorrelated noise powers.

If they were correlated, you would need to look at the noise amplitudes, but I don't think there is a way to specify them.

I think the only way to have correlated noise is to use noise source with a special circuit to contribute its value to different branches.

image

@topolarity
Copy link
Author

I agree with the interpretations above. For this case:

I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal");
I(a, b) <+ white_noise(G * 4 * P_K * $temperature, "thermal");

is equivalent to:

I(a, b) <+ sqrt(2) * white_noise(G * 4 * P_K * $temperature, "thermal");

And if the contributions had been re-used elsewhere so that there are observable correlations, such as:

N1 = white_noise(G * 4 * P_K * $temperature, "thermal");
N2 = white_noise(G * 4 * P_K * $temperature, "thermal");
I(a, b) <+ N1 + N2;
I(b, c) <+ N1;

then this cannot be reduced to a single independent noise source at all.

@gjcoram
Copy link

gjcoram commented Apr 3, 2024

@tcaduser is correct; the noise sources are uncorrelated. And @topolarity is also correct; having the two noise sources is equivalent to having sqrt(2) in front of a single noise source. This would be ns1 = sqrt(1), or ns2 = 2, in my earlier code.

@arpadbuermen
Copy link

Noise scaling should work now like @gjcoram explained earlier. Look in the openvaf-reloaded repository. Branches/osdi_0.3 works with ngspice.

@dwarning
Copy link

dwarning commented Oct 4, 2024

@arpadbuermen - see #137

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants