Distortion Analysis#
Analyze harmonic or spectral distortion in the circuit.
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 = "inverter" # stem of the .nyancir file
CORNER = "mos_tt"
# -- Distortion parameters --
VARIATION = "dec" # point spacing: "dec", "oct", or "lin"
NUM_POINTS = 10 # number of points
START_FREQ = 100 # start frequency (Hz)
STOP_FREQ = 1e4 # stop frequency (Hz)
F2_OVER_F1 = 0.9 # F2/F1 ratio for intermodulation (None for harmonic only)
SPECTRAL = False # enable spectral (intermodulation) analysis
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)
Duplicated lib ('ihp/models/ngspice/models/cornerMOSlv.lib', 'mos_tt')
.title schematic
.lib /Users/pepijndevos/code/IHP/ihp/models/ngspice/models/cornerMOSlv.lib mos_tt
VV1 W2 GND DC 1.8
VV2 W3 GND DC 0.9 AC 1 sin(0.9 0.5 1k)
XM1 W6 W3 GND GND sg13_lv_nmos l={0.13 * 1e-6} m=1 ng=1 w={0.15 * 1e-6}
XM2 W2 W3 W6 W2 sg13_lv_pmos l={0.13 * 1e-6} m=1 ng=1 w={0.15 * 1e-6}
CC1 W6 GND 1u
Run distortion 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)
analysis = simulation.distortion(
variation=VARIATION,
points=NUM_POINTS,
start_frequency=START_FREQ,
stop_frequency=STOP_FREQ,
f2overf1=F2_OVER_F1 if SPECTRAL else None,
)
print("Distortion analysis complete.")
Warning: can't find the initialization file spinit.
Newer Ngspice version that could be unsupported 46
Distortion analysis complete.
Results#
available = [*analysis.nodes.keys(), *analysis.branches.keys()]
print("Available vectors:", available)
df = pd.DataFrame(index=np.array(analysis.frequency))
for vec in analysis.nodes.keys():
df[vec] = np.array(analysis[vec])
df
Available vectors: ['n.xm1.nsg13_lv_nmos#SI', 'n.xm1.nsg13_lv_nmos#GP', 'n.xm1.nsg13_lv_nmos#NOI', 'n.xm2.nsg13_lv_pmos#SI', 'n.xm2.nsg13_lv_pmos#GP', 'n.xm2.nsg13_lv_pmos#NOI', 'w6', 'w3', 'w2', 'vv1', 'vv2']
| n.xm1.nsg13_lv_nmos#SI | n.xm1.nsg13_lv_nmos#GP | n.xm1.nsg13_lv_nmos#NOI | n.xm2.nsg13_lv_pmos#SI | n.xm2.nsg13_lv_pmos#GP | n.xm2.nsg13_lv_pmos#NOI | w6 | w3 | w2 | |
|---|---|---|---|---|---|---|---|---|---|
| 100.000000 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 125.892541 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 158.489319 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 199.526231 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 251.188643 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 316.227766 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 398.107171 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 501.187234 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 630.957344 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 794.328235 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 1000.000000 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 1258.925412 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 1584.893192 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 1995.262315 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 2511.886432 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 3162.277660 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 3981.071706 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 5011.872336 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 6309.573445 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 7943.282347 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
| 10000.000000 | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j | 0.0+0.0j |
plot = hv.NdOverlay(
{
k: hv.Curve((df.index, np.abs(df[k])), "Frequency (Hz)", "Distortion").opts(
logx=True, logy=True
)
for k in df.columns
}
)
plot.opts(responsive=True, height=500)
WARNING:param.OverlayPlot00356: Logarithmic axis range encountered value less than or equal to zero, please supply explicit lower bound to override default of 0.010.