SLiCAP noise analysis

../_images/colorCode.svg

SLiCAP noise analysis helps to create design equations for the noise performance of a circuit.

Noise can be assigned to independent voltage and current sources and to resistors. SLiCAP also has buit-in subcircuits with noise sources.

SLiCAP output displayed on this manual page, is generated with the script: noise.py, imported by Manual.py.

1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4"""
5noise.py: SLiCAP scripts for the HTML help file
6"""
7import SLiCAP as sl
8import sympy as sp
9import numpy as np

Noise parameters

During noise analysis, SLiCAP uses the noise parameter of independent voltage sources and independent current sources as uncorrelated noise sources. It inserts noise current sources in parallel with resistors with their noise spectrum determined by the parameters noisetemp and noiseflow.

10# Create a circuit object
11cir = sl.makeCircuit("kicad/noiseSources/noiseSources.kicad_sch")
../_images/noiseSources.svg
1noiseSources
2C1 out 0 C value={C_a} vinit=0
3I1 0 out I value=0 noise={S_i} dc=0 dcvar=0
4L1 1 out L value={L_a} iinit=0
5R1 1 2 R value={R_a} noisetemp={T} noiseflow={f_ell} dcvar=0 dcvarlot=0
6V1 2 0 V value=0 noise={S_v} dc=0 dcvar=0
7.end

Noise voltage sources

The noise parameter of independent voltage sources must be specified in \(\mathrm{\left[ \frac{V^2}{Hz} \right] }\)

In the circuit above, the spectral density of the voltage noise from V1 equals \(S_v\) \(\mathrm{\left[ \frac{V^2}{Hz} \right] }\).

Noise current sources

The noise parameter of independent current sources must be specified in \(\mathrm{\left[ \frac{A^2}{Hz} \right] }\)

In the circuit above, the spectral density of the curent noise from I1 equals \(S_i\) \(\mathrm{\left[ \frac{A^2}{Hz} \right] }\).

Resistors

The noise contributed by resistors is governed by two parameters:

  • noisetemp: The noise temperature of the resistor

  • noiseflow: The flicker noise corner frequency

Addition of noise current sources

Before executing a doNoise() instruction, SLiCAP adds noise current sources in parallel with resistors that have a nonzero positive noise temperature. These current sources obtain the reference designator I_noise_<resID>, where resID is the reference designator of the resistor. After the noise analysis, all independent current sources with reference designators starting with I_noise_ will be removed from the circuit.

The noise current spectral density \(S_\mathrm{I\_noise\_Rxx}\) of a resistor Rxx is set to:

\[S_\mathrm{I\_noise\_Rxx} = \frac{4kT_{noise_\mathrm{Rxx}}}{R_\mathrm{Rxx}} \left( 1 + \frac{f_{\ell_\mathrm{Rxx}}}{f}\right) \, \mathrm{\left[ \frac{A^2}{Hz}\right]}\]

where:

  • \(k\) is Boltzmann’s constant in [J/K]

  • \(T_{noise_\mathrm{Rxx}}\) is the noise temperature noisetemp of resistor Rxx in [K]

  • \(R_\mathrm{Rxx}\) is the resistance value of Rxx in [Ohm]

  • \(f_{\ell_\mathrm{Rxx}}\) is the flicker noise corner frequency flow of Rxx in [Hz]

In the circuit above:

  • the noise temperature of the resistor R1 is set to the gobal parameter \(T\)

  • the flicker noise corner frequency equals \(f_{\ell}\)

Hence, SLiCAP will insert a noise current source in parallel with R1 with a spectral density:

\[S_\mathrm{I\_noise\_R1} = \frac{4kT}{R_a} \left( 1 + \frac{f_\ell}{f} \right) \, \mathrm{\left[ \frac{A^2}{Hz} \right]}\]

Global parameters

Global parameters are defined in the file SLiCAPmodels.lib in the folder given by ini.main_lib_path. If global parameters are found in circuit element expressions or in circuit parameter definitions, SLiCAP automatically adds their definition to the circuit parameter definitions.

In the circuit above, the global parameter \(T\) is found in the noisetemp parameter of R1. Its definition with a default value of 300 K is added to the circuit parameter definitions:

13for par in cir.parDefs:
14    print(par, cir.parDefs[par])
T 300

Noise Analysis

At the beginning of the noise analysis, SLiCAP adds resistor noise current sources to the circuit (see above). This also adds the Boltzmann constant \(k\) to the circuit parameter definitions:

16noiseResult = sl.doNoise(cir, detector="V_out", source="V1")
17
18for par in cir.parDefs:
19    print(par, cir.parDefs[par])

This yields:

T 300
k 34516213/2500000000000000000000000000000

Important

The function doNoise() sets the noise attributes onoise, snoiseTerms, and onoiseTerms of the returned instruction object. The inoise and inoiseTerms attributes are only set if the circuit has a source specification.

Detector-referred noise

21onoise      = noiseResult.onoise
22print(onoise)

This yields:

4*T*k*(f + f_ell)*Abs(R_a**2)/(R_a*f*(16*pi**4*C_a**2*L_a**2*f**4 + 4*pi**2*C_a**2*R_a**2*f**2 - 8*pi**2*C_a*L_a*f**2 + 1)) + S_i*(4*pi**2*L_a**2*f**2 + R_a**2)*Abs(R_a**2)/(R_a**2*(16*pi**4*C_a**2*L_a**2*f**4 + 4*pi**2*C_a**2*R_a**2*f**2 - 8*pi**2*C_a*L_a*f**2 + 1)) + S_v*Abs(R_a**2)/(R_a**2*(16*pi**4*C_a**2*L_a**2*f**4 + 4*pi**2*C_a**2*R_a**2*f**2 - 8*pi**2*C_a*L_a*f**2 + 1))

Formatted output:

(1)\[\begin{split}\begin{align} S_{vno} = & \frac{4 R_{a} T k \left(f + f_{\ell}\right)}{f \left(16 \pi^{4} C_{a}^{2} L_{a}^{2} f^{4} + 4 \pi^{2} C_{a}^{2} R_{a}^{2} f^{2} - 8 \pi^{2} C_{a} L_{a} f^{2} + 1\right)} \nonumber \\ & + \frac{S_{i} \left(4 \pi^{2} L_{a}^{2} f^{2} + R_{a}^{2}\right)}{16 \pi^{4} C_{a}^{2} L_{a}^{2} f^{4} + 4 \pi^{2} C_{a}^{2} R_{a}^{2} f^{2} - 8 \pi^{2} C_{a} L_{a} f^{2} + 1} \nonumber \\ & + \frac{S_{v}}{16 \pi^{4} C_{a}^{2} L_{a}^{2} f^{4} + 4 \pi^{2} C_{a}^{2} R_{a}^{2} f^{2} - 8 \pi^{2} C_{a} L_{a} f^{2} + 1} \,\left[\mathrm{\frac{V^2}{Hz}}\right] \end{align}\end{split}\]

Source-referred noise

24inoise      = noiseResult.inoise
25print(inoise)

This yields:

4*R_a*T*k*(f + f_ell)/f + S_i*(4*pi**2*L_a**2*f**2 + R_a**2) + S_v

Formatted output:

(2)\[S_{vni} = \frac{4 R_{a} T k \left(f + f_{\ell}\right)}{f} + S_{i} \left(4 \pi^{2} L_{a}^{2} f^{2} + R_{a}^{2}\right) + S_{v}\,\,\left[\mathrm{\frac{V^2}{Hz}}\right]\]

Contributions to detector-referred and source-referred noise

27snoiseTerms = noiseResult.snoiseTerms
28onoiseTerms = noiseResult.onoiseTerms
29inoiseTerms = noiseResult.inoiseTerms
30for term in snoiseTerms:
31    print("\n= " + term + " =")
32    print("value        :", snoiseTerms[term])
33    print("det-referred :", onoiseTerms[term])
34    print("src-referred :", inoiseTerms[term])

This yields:

= I1 =
value        : S_i
det-referred : S_i*(4*pi**2*L_a**2*f**2 + R_a**2)*Abs(R_a**2)/(R_a**2*(16*pi**4*C_a**2*L_a**2*f**4 + 4*pi**2*C_a**2*R_a**2*f**2 - 8*pi**2*C_a*L_a*f**2 + 1))
src-referred : S_i*(4*pi**2*L_a**2*f**2 + R_a**2)

= V1 =
value        : S_v
det-referred : S_v*Abs(R_a**2)/(R_a**2*(16*pi**4*C_a**2*L_a**2*f**4 + 4*pi**2*C_a**2*R_a**2*f**2 - 8*pi**2*C_a*L_a*f**2 + 1))
src-referred : S_v

= I_noise_R1 =
value        : 4*T*k*(1 + f_ell/f)/R_a
det-referred : 4*T*k*(f + f_ell)*Abs(R_a**2)/(R_a*f*(16*pi**4*C_a**2*L_a**2*f**4 + 4*pi**2*C_a**2*R_a**2*f**2 - 8*pi**2*C_a*L_a*f**2 + 1))
src-referred : 4*R_a*T*k*(f + f_ell)/f

Formatted output:

Value

Units

I1: Source value

\(S_{i}\)

\(\mathrm{\frac{A^2}{Hz}}\)

I1: Source-referred

\(S_{i} \left(4 \pi^{2} L_{a}^{2} f^{2} + R_{a}^{2}\right)\)

\(\mathrm{\frac{V^2}{Hz}}\)

I1: Detector-referred

\(\frac{S_{i} \left(4 \pi^{2} L_{a}^{2} f^{2} + R_{a}^{2}\right)}{16 \pi^{4} C_{a}^{2} L_{a}^{2} f^{4} + 4 \pi^{2} C_{a}^{2} R_{a}^{2} f^{2} - 8 \pi^{2} C_{a} L_{a} f^{2} + 1}\)

\(\mathrm{\frac{V^2}{Hz}}\)

V1: Source value

\(S_{v}\)

\(\mathrm{\frac{V^2}{Hz}}\)

V1: Source-referred

\(S_{v}\)

\(\mathrm{\frac{V^2}{Hz}}\)

V1: Detector-referred

\(\frac{S_{v}}{16 \pi^{4} C_{a}^{2} L_{a}^{2} f^{4} + 4 \pi^{2} C_{a}^{2} R_{a}^{2} f^{2} - 8 \pi^{2} C_{a} L_{a} f^{2} + 1}\)

\(\mathrm{\frac{V^2}{Hz}}\)

I_noise_R1: Source value

\(\frac{4 T k \left(1 + \frac{f_{\ell}}{f}\right)}{R_{a}}\)

\(\mathrm{\frac{A^2}{Hz}}\)

I_noise_R1: Source-referred

\(\frac{4 R_{a} T k \left(f + f_{\ell}\right)}{f}\)

\(\mathrm{\frac{V^2}{Hz}}\)

I_noise_R1: Detector-referred

\(\frac{4 R_{a} T k \left(f + f_{\ell}\right)}{f \left(16 \pi^{4} C_{a}^{2} L_{a}^{2} f^{4} + 4 \pi^{2} C_{a}^{2} R_{a}^{2} f^{2} - 8 \pi^{2} C_{a} L_{a} f^{2} + 1\right)}\)

\(\mathrm{\frac{V^2}{Hz}}\)

Subcircuits with noise

Built-in subcircuits

Below an overview of subcircuits and symbols for noise analysis. Subcircuits are defined in the library SLiCAP.lib in the folder indicated by ini.main_lib_path.

subcircuit name

description

parameters

KiCAD

gschem/Lepton-EDA

LTspice

N_noise

Nullor with equivalent-input noise sources

si, sv

N_noise

N_noise

SLN_noise

O_noise

Nullor with equivalent-input noise sources

si, sv

O_noise

O_noise

SLO_noise

NM18_noise

NMOS 180nm equivalent-input noise EKV model

ID, IG, W, L

M_noise

M_noise

SLM_noise

PM18_noise

PMOS 180nm equivalent-input noise EKV model

ID, IG, W, L

M_noise

M_noise

SLM_noise

NM18_noisyNullor

Nullor with NMOS 180nm equivalent-input noise EKV model

ID, IG, W, L

XM_noisyNullor

XM_noisyNullor

SLM_noisyNullor

PM18_noisyNullor

Nullor with PMOS 180nm equivalent-input noise EKV model

ID, IG, W, L

XM_noisyNullor

XM_noisyNullor

SLM_noisyNullor

J_noise

MOS/JFET equivalent-input noise sources

ID, IG, W, L

J_noise

M_noise

SLM_noise

Q_noise

BJT equivalent-input noise sources, r_b=0

IC, VCE

Q_noise

Q_noise

SLQ_noise

Wide table: slide below the table!

Create noise elements

The user can create noisy circuits or subcircuits using independent noise sources, independend current sources, resistors and predefined noisy subcircuits from above.

Noise post processing functions

RMS noise

SLiCAP can perform symbolic and numeric integration and calculate the source-referred or detector-referred rmsNoise .

rmsNoise() calculates the RMS value by integrating each of the noise terms in the onoiseTerms or the inoiseTerms attribute of the noise analysis result. For each term it tries to find the best integration method. However, success is not always guaranteed:

  • Symbolic integration of functions with many variables, may take very long

  • Symbolic integration of functions is not always possible or implemented in Sympy.

The following may help:

  • Keep the circuit model as simple as possible and use only one or two symbolic design parameters.

  • Calculate the variance instead of the RMS noise (standard deviation), and try to obtain expressions with symbolic variables as coefficients of numeric integrals (see below).

Below an example how to express the RMS noise in terms of the source spectral densities \(S_v\) and \(S_i\).

36params = {"R_a"  : "1k",
37          "C_a"  : "10u",
38          "L_a"  : "1m",
39          "f_ell": 100}
40
41cir.defPars(params)
42
43numNoise = sl.doNoise(cir, detector="V_out", pardefs="circuit", numeric=True)
44
45f_min = 1e3
46f_max = 1e6
47
48RMS = sl.rmsNoise(numNoise, 'onoise', f_min, f_max)
49print(RMS)

This yields:

711.447029712076*sqrt(S_i + 4.95576373211838e-7*S_v + 7.6162627945633e-24)

Typesetted:

\[V_{nRMS} = 711.4 \left(S_{i} + 4.956 \cdot 10^{-7} S_{v} + 7.616 \cdot 10^{-24}\right)^{0.5}\,\,\left[\mathrm{V}\right]\]

Noise Figure

The noise figure of a system is a measure for the deterioration of the signal-to-noise ratio by that system. It can be found as the ratio of the total weighted output noise power (variance) and the contribution of the signal source noise to it. Alternatively, one can take the ratio of the total weighted source-referred noise and the weighted source noise.

51# noise figure; V1 is signal source, with associated noise spectrum S_v
52o_var   = RMS**2
53src_var = sl.rmsNoise(numNoise, 'onoise', f_min, f_max, source="V1")**2
54noiseF  = sp.expand(o_var/src_var)
55print(noiseF)

This yields:

2017852.45232533*S_i/S_v + 1.0 + 1.53684945575637e-17/S_v

Commonly the noise figure is expressed in [dB]:

56F       = 10*sp.log(noiseF)/sp.log(10)
57print(F)

This yields:

10*log(2017852.45232533*S_i/S_v + 1.0 + 1.53684945575637e-17/S_v)/log(10)

Typesetted:

\[F = \frac{10 \log{\left(\frac{2.018 \cdot 10^{6} S_{i}}{S_{v}} + 1 + \frac{1.537 \cdot 10^{-17}}{S_{v}} \right)}}{\log{\left(10 \right)}}\,\,\left[\mathrm{dB}\right]\]

Noise weighting functions

DIN A

SLiCAP has the frequency weighting function DIN_A(), which is often used for spectral weighting of noise and distortion components in audio applications. See WiKi R_A(f).

59print(sl.DIN_A())
18719114681919*f**4/(100000*sqrt((f**2 + 1159929/100)*(f**2 + 54449641/100))*(f**2 + 10609/25)*(f**2 + 148693636))
61# Plot the DIN A weighting curve
62frequencies = np.geomspace(10, 20e3, 200, endpoint=True)
63din_A_func  = sp.lambdify(sl.ini.frequency, 20*sp.log(sl.DIN_A())/sp.log(10))
64din_A_num   = din_A_func(frequencies)
65din_A_trace = sl.trace([frequencies, din_A_num])
66din_A_trace.label = "DIN A"
67traceDict   = {"DINA": din_A_trace}
68sl.plot("DINA", "DIN A weighting curve", "semilogx", traceDict, 
69        xName="Frequency", xUnits="Hz", yUnits="dB", show=False)
../_images/DINA.svg

Correlated Double Sampling

Correlated Double Sampling is a technique that suppresses low-frequency noise by periodically taking the difference between two signal samples at a fixed time difference. It is applied in switched integrators used in optical detectors and image sensors.

The function doCDS(), takes a noise spectrum and a time delay as argument and returns the CDS weighted noise spectrum.

71S_v, tau     = sp.symbols("S_v, tau")
72cds_weighted = sl.doCDS(S_v, tau)
73print(cds_weighted)

This yields:

4*S_v*sin(pi*f*tau)**2

Typesetted:

\[S_{vCDS} = 4 S_{v} \sin^{2}{\left(\pi f \tau \right)}\,\,\left[\mathrm{\frac{V^2}{Hz}}\right]\]

Important

The function doCDS() multiplies the input spectrum with the squared magnitude of the CDS transfer function. Hence, the input spectrum must be in \(\mathrm{\left[ \frac{V^2}{Hz}\right]}\), \(\mathrm{\left[ \frac{A^2}{Hz}\right]}\) or \(\mathrm{\left[ \frac{W}{Hz}\right]}\). Use the onoise or inoise attribute of the noise analysis result!

75# Plot the CDS weighting curve for tau = 1 us
76freqs       = np.geomspace(5e3, 4.5e6, 1000)
77cds_func  = sp.lambdify(sl.ini.frequency, 10*sp.log(sl.doCDS(sp.N(1), 1e-6))/sp.log(10))
78cds_num   = cds_func(freqs)
79cds_trace = sl.trace([freqs, cds_num])
80cds_trace.label = "CDS"
81traceDict   = {"CDS": cds_trace}
82sl.plot("CDS", "CDS weighting curve", "semilogx", traceDict, 
83        xName="Frequency", xUnits="Hz", yUnits="dB", show=False)
../_images/CDS.svg

The function doCDSint performs both CDS filtering and integration over frequency.

Below the script that calculates the variance of the output voltage of a switched transimpedance integrator with a gain \(Z_t=\frac{1}{2\pi f C_i}\) and an input noise current with a spectral density \(S_i\) to which CDS filtering of the output signal with a time delay \(\tau\) is applied.

85S_v = sp.sympify("S_i/(2*pi*f*C_i)^2")
86var = sl.doCDSint(S_v, tau, 0, sp.oo)
87print(var)

SLiCAP uses \(\_\phi=\pi f \tau\) rather than \(f\) as integration variable. Integration is not always possible because the integral may depend on the domain of the variables:

S_i*tau*Integral(sin(_phi)**2/_phi**2, (_phi, 0, oo*tau))/(pi*C_i**2)

As shown above, the integral cannot be evaluated (undefined when \(\tau=0\). However, it can be done with the aid of assumptions.

Use assumptions

Below the script to obtain the integral from above.

89var = sl.assumePosParams(var).doit()
90var = sl.clearAssumptions(var)
91print(var)

With \(\tau>0\) the above integral can be evaluated:

S_i*tau/(2*C_i**2)

Typesetted:

\[\sigma^{2} = \frac{0.5 S_{i} \tau}{C_{i}^{2}}\,\,\left[\mathrm{V^2}\right]\]

The following SLiCAP functions can be used to change the domain of variables:

  1. assumePosParams()

  2. assumeRealParams()

  3. clearAssumptions()

Obtain symbolic coefficients of numeric intergals

Sometimes numeric integration over frequency becomes possible after symbolic design parameters have been elimated from the integral.

The function integrate_monomial_coeffs() returns an experssion in which monomials of two selected parameters have been taken out of the integral:

93Si, Sv = sp.symbols("S_i, S_v")
94integrated_noise   = sl.integrate_monomial_coeffs(numNoise.onoise, [Si, Sv], 
95                                                  sl.ini.frequency, f_min, f_max, 
96                                                  doit=True)

This yields:

253078.438043065*S_i + 0.250839388927001*S_v + 3.85502378354722e-18

Typesetted:

\[v_{n}^{2} = 2.531 \cdot 10^{5} S_{i} + 0.2508 S_{v} + 3.855 \cdot 10^{-18}\,\,\left[\mathrm{V^2}\right]\]

The function integrated_monomial_coeffs() returns a dictionary of which the keys are monomials consisting of two variables, and the coefficients of these monomials are numeric integrals.

 99integrated_noise_coeffs = sl.integrated_monomial_coeffs(numNoise.onoise, [Si, Sv], 
100                                       sl.ini.frequency, f_min, f_max, doit=False)

Typesetted output:

Table 20 Numeric integrals as coeffs of \(S_v\) and \(S_i\)

\(S_{i}\)

\(\int\limits_{1000}^{1.0 \cdot 10^{6}} \frac{1.0 \cdot 10^{38} \left(3.948 \cdot 10^{29} f^{2} + 1.0 \cdot 10^{40}\right)}{1.559 \cdot 10^{59} f^{4} + 3.947 \cdot 10^{69} f^{2} + 1.0 \cdot 10^{72}}\, df\)

\(S_{v}\)

\(\int\limits_{1000}^{1.0 \cdot 10^{6}} \frac{1.0 \cdot 10^{72}}{1.559 \cdot 10^{59} f^{4} + 3.947 \cdot 10^{69} f^{2} + 1.0 \cdot 10^{72}}\, df\)

\(1\)

\(\int\limits_{1000}^{1.0 \cdot 10^{6}} \frac{1.657 \cdot 10^{55} \left(f + 100\right)}{f \left(1.559 \cdot 10^{59} f^{4} + 3.947 \cdot 10^{69} f^{2} + 1.0 \cdot 10^{72}\right)}\, df\)

Display equations on HTML pages and in LaTeX documents

The report module Create reports, discusses how HTML snippets and LaTeX snippets can be created for variables, expressions, equations and tables.

As a matter of fact, all equations, tables and figures on this page are created with this module:

102# Create an RST formatter
103rst = sl.RSTformatter()
104# Save expressions in the sphinx/SLiCAPdata folder of the project directory
105rst.eqn("S_vno", onoise, multiline=1, label="eqn-Svno", units="V**2/Hz").save("eqn-Svno")
106rst.eqn("S_vni", inoise, label="eqn-Svni", units="V**2/Hz").save("eqn-Svni")
107rst.noiseContribs(noiseResult, "Noise contributions").save("table-noiseContribs")
108rst.eqn("V_nRMS", RMS, units="V").save("eqn-RMS")
109rst.eqn("F", F, units="dB").save("eqn-F")
110rst.eqn("S_vCDS", cds_weighted, units="V**2/Hz").save("eqn-CDSweighting")
111rst.eqn("sigma^2", var, units="V**2").save("eqn-CDSint")
112rst.eqn("v_n^2", integrated_noise, units="V**2").save("eqn-intCoeffs")
113rst.dictTable(integrated_noise_coeffs, 
114              caption="Numeric integrals as coeffs of :math:`S_v` and :math:`S_i`").save("table-coeffsNoise")

The typesetted tables and equations are shown on this page.

Plot noise spectrum

SLiCAP plot functions are discussed in Create plots.

../_images/colorCode.svg