Germanium diode AM demodulator

ground 0 

load "diode.osdi"
load "resistor.osdi"
load "capacitor.osdi"

model vsource vsource
model d310 diode(
    is=2.5u n=1.437 rs=0.07 m=0.314 vj=1.65 
    cjo=14pF ibv=5u bv=20 tt=130n eg=0.67 xti=3
)
model resistor resistor
model capacitor capacitor

v2 (1 0) vsource type="am" sinedc=0.0 ampl=0.8 freq=50k modfreq=1k modindex=0.5
d1 (1 2) d310
c1 (2 0) capacitor c=1n
r1 (2 0) resistor r=200k

control
  abort always
  options pss_debug=1
  options pss_itl=50
  options pss_tolscale=0.5
  analysis pss1 pss driven=1 tper=2m tstab=20m writestab=1 maxacfreq=1.975M
  
  postprocess(PYTHON, "runme.py")
endc


embed "runme.py" <<<FILE
from rawfile import rawread
from runtest import *
import numpy as np
import sys

# -------------------------------------------------------------------
# Transient results
# -------------------------------------------------------------------
plot  = rawread('pss1.tran.raw').get()
t     = plot['time']
v2    = plot['2']
i1    = -plot['v2:flow(br)']

tests = []

status = bool(np.all(np.isfinite(v2)) and len(t) > 10)
tests.append(status)
print(f"Tran: simulation completed ({len(t)} points): {status}")

n_half = len(t) // 2
v1_2h  = v2[n_half:]
amp_pp = float(np.max(v1_2h) - np.min(v1_2h))
status = amp_pp > 0.1
tests.append(status)
print(f"Tran: peak-to-peak in 2nd half = {amp_pp*1e3:.1f} mV (expect > 100 mV): {status}")

# -------------------------------------------------------------------
# PSS results (gracefully skipped if PSS not yet converging)
# -------------------------------------------------------------------
pss_ok = False
try:
    ppss  = rawread('pss1.raw').get()
    tp    = ppss['time']
    vp    = ppss['2']
    ip    = -ppss['v2:flow(br)']
    T0    = float(tp[-1] - tp[0])
    f0    = 1.0 / T0
    vpp   = float(vp.max() - vp.min())
    print(f"PSS:  converged T0={T0:.4e} s  f0={f0:.1f} Hz  Vpp={vpp*1e3:.0f} mV")
    pss_ok = True
except Exception as e:
    print(f"PSS:  not available ({e})")

status = pss_ok
tests.append(status)
print(f"PSS:  converged: {status}")

# -------------------------------------------------------------------
# Spectrum from PSS waveform (exact harmonics, no leakage)
# -------------------------------------------------------------------
if pss_ok:
    N_fft  = 1024
    tp_uni = np.linspace(float(tp[0]), float(tp[0]) + T0, N_fft, endpoint=False)
    vp_uni = np.interp(tp_uni, tp, vp)
    fmag   = np.abs(np.fft.rfft(vp_uni)) * (2.0 / N_fft)
    fmag[0] /= 2                             # DC bin is one-sided already
    freqs  = np.arange(len(fmag)) * f0      # harmonic frequencies in Hz
    vph = np.angle(np.fft.rfft(vp_uni)) * 180 / np.pi

    match = np.abs(vp[-1]-vp[0])/np.abs(vp).max()
    ok    = match<1e-6
    print("Endpoint relative delta:", match, "PASS" if ok else "")
    tests.append(ok)

if isTest():
    sys.exit(not all(tests))
elif pss_ok:
    import matplotlib.pyplot as plt

    n_panels = 3
    fig, axes = plt.subplots(n_panels, 1, figsize=(9, 4 * n_panels), constrained_layout=True)
    fig.suptitle("PSS for driven Germanium diode AM demodulator")

    axes[0].plot(tp * 1e3, vp, lw=1.2, color='C1')
    axes[0].axhline(vp[0], ls='--', alpha=0.2)
    axes[0].set_xlabel("time [ms]")
    axes[0].set_ylabel("V(2) [V]")
    axes[0].set_title(f"PSS periodic response  f0={f0:.1f} Hz")

    axes[1].stem(freqs / 1e3, fmag)
    axes[1].set_yscale("log")
    axes[1].set_xlabel("frequency [kHz]")
    axes[1].set_ylabel("|FFT| amplitude [V]")
    axes[1].set_title("PSS spectrum")

    axes[2].stem(freqs / 1e3, vph)
    axes[2].set_xlabel("frequency [kHz]")
    axes[2].set_ylabel("Phase [deg]")
    axes[2].set_title("PSS spectrum (phase)")

    plt.show()
>>>FILE