Noise Analysis#
Perform stochastic noise analysis at the DC operating point.
import os
import sys
from pathlib import Path
# Set working directory to the PDK root if running from scripts/
if Path.cwd().name == "scripts":
os.chdir(Path.cwd().parent)
# Ensure ngspice shared library can be found (macOS homebrew)
if sys.platform == "darwin" and "/opt/homebrew/lib" not in os.environ.get(
"DYLD_LIBRARY_PATH", ""
):
os.environ["DYLD_LIBRARY_PATH"] = "/opt/homebrew/lib:" + os.environ.get(
"DYLD_LIBRARY_PATH", ""
)
import numpy as np
import pandas as pd
import holoviews as hv
from nyancad.watch import watch_project_dir, file_schematic
from nyancad.netlist import inspice_netlist
from InSpice import Simulator
hv.extension("bokeh")
Parameters#
SCHEMATIC = "rcfilter" # stem of the .nyancir file
CORNER = "mos_tt"
# -- Noise parameters --
OUTPUT_NODE = "P2" # output node
REF_NODE = "GND" # reference node
INPUT_SOURCE = "VV1" # input source name
VARIATION = "dec" # point spacing: "dec", "oct", or "lin"
NUM_POINTS = 10 # number of points
START_FREQ = 10 # start frequency (Hz)
STOP_FREQ = 1e5 # stop frequency (Hz)
POINTS_PER_SUMMARY = 0 # points per summary (0 for no summary)
Load schematic and build netlist#
project = watch_project_dir(".")
schem_data = await file_schematic(project, SCHEMATIC)
spice = await inspice_netlist(SCHEMATIC, schem_data, corner=CORNER)
print(spice)
.title schematic
VV1 net0 GND DC 0 AC 1
CC1 P2 net0 1u
RR1 P2 GND 1k
print("Netlist nodes:", list(spice.node_names))
print("Netlist elements:", list(spice.element_names))
Netlist nodes: ['0', 'net0', 'GND', 'P2']
Netlist elements: ['VV1', 'CC1', 'RR1']
Run noise analysis#
simulator = Simulator.factory(simulator="ngspice-shared")
for osdi in [
"ihp/models/ngspice/osdi/psp103.osdi",
"ihp/models/ngspice/osdi/psp103_nqs.osdi",
"ihp/models/ngspice/osdi/r3_cmc.osdi",
"ihp/models/ngspice/osdi/mosvar.osdi",
]:
simulator._ngspice_shared.exec_command(f"osdi {osdi}")
simulation = simulator.simulation(spice)
simulation.noise(
output_node=OUTPUT_NODE,
ref_node=REF_NODE,
src=INPUT_SOURCE,
variation=VARIATION,
points=NUM_POINTS,
start_frequency=START_FREQ,
stop_frequency=STOP_FREQ,
points_per_summary=POINTS_PER_SUMMARY if POINTS_PER_SUMMARY > 0 else None,
)
print("Noise analysis complete.")
# ngspice 46 populates two plots: noise1 (spectrum vs frequency) and noise2
# (integrated totals). InSpice's shared-lib plot() can't read noise1's vectors
# directly on ngspice 46, so use ngspice's wrdata to dump them to a file.
import os as _os
import tempfile
_out = tempfile.NamedTemporaryFile(suffix=".data", delete=False).name
simulator._ngspice_shared.exec_command(
f"wrdata {_out} noise1.frequency noise1.inoise_spectrum noise1.onoise_spectrum"
)
_raw = np.loadtxt(_out)
_os.unlink(_out)
freq = _raw[:, 0]
inoise_spectrum = _raw[:, 3]
onoise_spectrum = _raw[:, 5]
totals = simulator._ngspice_shared.plot(simulation, "noise2").to_analysis()
print(f"Integrated input-referred noise: {float(np.array(totals.inoise_total)):.3e}")
print(f"Integrated output-referred noise: {float(np.array(totals.onoise_total)):.3e}")
Warning: can't find the initialization file spinit.
Newer Ngspice version that could be unsupported 46
Noise analysis complete.
Integrated input-referred noise: 1.847e-07
Integrated output-referred noise: 6.297e-08
/var/folders/f6/0bfsmxqs4dd6486gr3438tnw0000gn/T/ipykernel_11336/3717702115.py:38: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
print(f"Integrated input-referred noise: {float(np.array(totals.inoise_total)):.3e}")
/var/folders/f6/0bfsmxqs4dd6486gr3438tnw0000gn/T/ipykernel_11336/3717702115.py:39: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
print(f"Integrated output-referred noise: {float(np.array(totals.onoise_total)):.3e}")
Results#
df = pd.DataFrame(
{"inoise_spectrum": inoise_spectrum, "onoise_spectrum": onoise_spectrum},
index=pd.Index(freq, name="frequency"),
)
df
| inoise_spectrum | onoise_spectrum | |
|---|---|---|
| frequency | ||
| 10.000000 | 6.479789e-08 | 4.063359e-09 |
| 12.589254 | 5.147079e-08 | 4.058694e-09 |
| 15.848932 | 4.088470e-08 | 4.051333e-09 |
| 19.952623 | 3.247588e-08 | 4.039750e-09 |
| 25.118864 | 2.579650e-08 | 4.021592e-09 |
| 31.622777 | 2.049089e-08 | 3.993310e-09 |
| 39.810717 | 1.627649e-08 | 3.949682e-09 |
| 50.118723 | 1.292888e-08 | 3.883374e-09 |
| 63.095734 | 1.026977e-08 | 3.784799e-09 |
| 79.432823 | 8.157571e-09 | 3.642869e-09 |
| 100.000000 | 6.479789e-09 | 3.447365e-09 |
| 125.892541 | 5.147079e-09 | 3.193168e-09 |
| 158.489319 | 4.088470e-09 | 2.884921e-09 |
| 199.526231 | 3.247588e-09 | 2.538830e-09 |
| 251.188643 | 2.579650e-09 | 2.179068e-09 |
| 316.227766 | 2.049089e-09 | 1.830344e-09 |
| 398.107171 | 1.627649e-09 | 1.511350e-09 |
| 501.187234 | 1.292888e-09 | 1.232249e-09 |
| 630.957344 | 1.026977e-09 | 9.957865e-10 |
| 794.328235 | 8.157571e-10 | 7.998596e-10 |
| 1000.000000 | 6.479789e-10 | 6.399248e-10 |
| 1258.925410 | 5.147079e-10 | 5.106435e-10 |
| 1584.893190 | 4.088470e-10 | 4.068011e-10 |
| 1995.262310 | 3.247588e-10 | 3.237305e-10 |
| 2511.886430 | 2.579650e-10 | 2.574488e-10 |
| 3162.277660 | 2.049089e-10 | 2.046499e-10 |
| 3981.071710 | 1.627649e-10 | 1.626350e-10 |
| 5011.872340 | 1.292888e-10 | 1.292236e-10 |
| 6309.573440 | 1.026977e-10 | 1.026651e-10 |
| 7943.282350 | 8.157571e-11 | 8.155934e-11 |
| 10000.000000 | 6.479789e-11 | 6.478969e-11 |
| 12589.254100 | 5.147079e-11 | 5.146668e-11 |
| 15848.931900 | 4.088470e-11 | 4.088264e-11 |
| 19952.623100 | 3.247588e-11 | 3.247484e-11 |
| 25118.864300 | 2.579650e-11 | 2.579599e-11 |
| 31622.776600 | 2.049089e-11 | 2.049063e-11 |
| 39810.717100 | 1.627649e-11 | 1.627636e-11 |
| 50118.723400 | 1.292888e-11 | 1.292881e-11 |
| 63095.734400 | 1.026977e-11 | 1.026974e-11 |
| 79432.823500 | 8.157571e-12 | 8.157555e-12 |
| 100000.000000 | 6.479789e-12 | 6.479781e-12 |
plot = hv.NdOverlay(
{
k: hv.Curve((df.index, df[k]), "Frequency (Hz)", "Noise spectral density").opts(
logx=True, logy=True
)
for k in df.columns
}
)
plot.opts(responsive=True, height=500)