From d5bfe3af4a0c071a043ad99a0d6eabdd4680c68f Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Mon, 23 Mar 2026 21:20:57 +0100 Subject: [PATCH] OTA (operational transconductance amplifier) code model as a reference implementation that exercises the programmatic noise interface. --- src/xspice/icm/analog/ota/cfunc.mod | 110 ++++++++++++++++++++++++++ src/xspice/icm/analog/ota/ifspec.ifs | 114 +++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 src/xspice/icm/analog/ota/cfunc.mod create mode 100644 src/xspice/icm/analog/ota/ifspec.ifs diff --git a/src/xspice/icm/analog/ota/cfunc.mod b/src/xspice/icm/analog/ota/cfunc.mod new file mode 100644 index 000000000..e82ee0e16 --- /dev/null +++ b/src/xspice/icm/analog/ota/cfunc.mod @@ -0,0 +1,110 @@ +/* +================================================================================ + +FILE ota/cfunc.mod + +Public Domain + +AUTHORS + + 20 Mar 2026 Seth Hillbrand + +SUMMARY + + This file contains the model-specific routines for the OTA + (Operational Transconductance Amplifier) code model. + + DC/TRAN: Vout as current = gm * (Vp - Vn) + AC: Complex gain = gm + NOISE: Programmatic noise with en, in_noise, enk, ink, incm, incmk. + + Parameter naming follows LTSPICE convention for OTA compatibility. + +================================================================================ +*/ + +#include + + +void cm_ota(ARGS) +{ + double gm_val = PARAM(gm); + double rout = PARAM(rout); + double rin = PARAM(rin); + + Mif_Complex_t ac_gain; + + if (ANALYSIS == NOISE) { + /* Register noise sources on every call (N_OPEN and N_CALC). + * During N_OPEN: registers and returns index. + * During N_CALC: returns same sequential index. */ + int src_en_w = cm_noise_add_source("en_white", 1, 0, MIF_NOISE_CURRENT); + int src_en_f = cm_noise_add_source("en_flicker", 1, 0, MIF_NOISE_CURRENT); + int src_in_w = cm_noise_add_source("in_white", 0, 0, MIF_NOISE_CURRENT); + int src_in_f = cm_noise_add_source("in_flicker", 0, 0, MIF_NOISE_CURRENT); + + /* Common-mode current noise: independent sources from each input pin to ground */ + int src_icm_pw = cm_noise_add_source("incm_p_white", 0, 0, MIF_NOISE_CURRENT_POS); + int src_icm_pf = cm_noise_add_source("incm_p_flicker", 0, 0, MIF_NOISE_CURRENT_POS); + int src_icm_nw = cm_noise_add_source("incm_n_white", 0, 0, MIF_NOISE_CURRENT_NEG); + int src_icm_nf = cm_noise_add_source("incm_n_flicker", 0, 0, MIF_NOISE_CURRENT_NEG); + + if (!mif_private->noise->registering) { + double en = PARAM(en); + double in_n = PARAM(in_noise); + double enk_val = PARAM(enk); + double ink_val = PARAM(ink); + double incm = PARAM(incm); + double incmk_val = PARAM(incmk); + double f = NOISE_FREQ; + + /* en referred to output as current noise: (en * gm)^2 A^2/Hz */ + NOISE_DENSITY(src_en_w) = en * en * gm_val * gm_val; + NOISE_DENSITY(src_en_f) = (enk_val > 0 && f > 0) ? + en * en * gm_val * gm_val * enk_val / f : 0.0; + + /* in as differential current noise at input: in^2 A^2/Hz */ + NOISE_DENSITY(src_in_w) = in_n * in_n; + NOISE_DENSITY(src_in_f) = (ink_val > 0 && f > 0) ? + in_n * in_n * ink_val / f : 0.0; + + /* incm as current noise from each input pin to ground */ + NOISE_DENSITY(src_icm_pw) = incm * incm; + NOISE_DENSITY(src_icm_pf) = (incmk_val > 0 && f > 0) ? + incm * incm * incmk_val / f : 0.0; + NOISE_DENSITY(src_icm_nw) = incm * incm; + NOISE_DENSITY(src_icm_nf) = (incmk_val > 0 && f > 0) ? + incm * incm * incmk_val / f : 0.0; + } + + return; + } + + if (ANALYSIS != MIF_AC) { + double v_in = INPUT(inp); + double v_out = INPUT(out); + + OUTPUT(out) = gm_val * v_in + v_out / rout; + PARTIAL(out, inp) = gm_val; + PARTIAL(out, out) = 1.0 / rout; + + OUTPUT(inp) = v_in / rin; + PARTIAL(inp, inp) = 1.0 / rin; + } + else { + Mif_Complex_t ac_rout; + Mif_Complex_t ac_rin; + + ac_gain.real = gm_val; + ac_gain.imag = 0.0; + AC_GAIN(out, inp) = ac_gain; + + ac_rout.real = 1.0 / rout; + ac_rout.imag = 0.0; + AC_GAIN(out, out) = ac_rout; + + ac_rin.real = 1.0 / rin; + ac_rin.imag = 0.0; + AC_GAIN(inp, inp) = ac_rin; + } +} diff --git a/src/xspice/icm/analog/ota/ifspec.ifs b/src/xspice/icm/analog/ota/ifspec.ifs new file mode 100644 index 000000000..0a6721a09 --- /dev/null +++ b/src/xspice/icm/analog/ota/ifspec.ifs @@ -0,0 +1,114 @@ +/* +================================================================================ +Public Domain + +SUMMARY + + Interface specification for the OTA (Operational Transconductance Amplifier) + code model. LTSPICE-compatible parameter naming convention. + + Noise is implemented via the programmatic API (cm_noise_add_source / + NOISE_DENSITY). Set noise_programmatic = TRUE to enable noise analysis. + +================================================================================ +*/ + +NAME_TABLE: + + +C_Function_Name: cm_ota +Spice_Model_Name: ota +Description: "Operational Transconductance Amplifier with noise" + + +PORT_TABLE: + + +Port_Name: inp out +Description: "input" "output" +Direction: inout inout +Default_Type: gd g +Allowed_Types: [gd] [g] +Vector: no no +Vector_Bounds: - - +Null_Allowed: no no + + +PARAMETER_TABLE: + + +Parameter_Name: gm rout +Description: "transconductance" "output resistance" +Data_Type: real real +Default_Value: 1.0e-3 1.0e12 +Limits: [1e-15 -] [0 -] +Vector: no no +Vector_Bounds: - - +Null_Allowed: yes yes + + +PARAMETER_TABLE: + + +Parameter_Name: rin +Description: "input resistance" +Data_Type: real +Default_Value: 1.0e12 +Limits: [0 -] +Vector: no +Vector_Bounds: - +Null_Allowed: yes + + +PARAMETER_TABLE: + + +Parameter_Name: en in_noise +Description: "input voltage noise density V/rtHz" "input current noise density A/rtHz" +Data_Type: real real +Default_Value: 0.0 0.0 +Limits: [0 -] [0 -] +Vector: no no +Vector_Bounds: - - +Null_Allowed: yes yes + + +PARAMETER_TABLE: + + +Parameter_Name: enk ink +Description: "voltage noise 1/f corner Hz" "current noise 1/f corner Hz" +Data_Type: real real +Default_Value: 0.0 0.0 +Limits: [0 -] [0 -] +Vector: no no +Vector_Bounds: - - +Null_Allowed: yes yes + + +PARAMETER_TABLE: + + +Parameter_Name: incm incmk +Description: "CM current noise density A/rtHz" "CM current noise 1/f corner Hz" +Data_Type: real real +Default_Value: 0.0 0.0 +Limits: [0 -] [0 -] +Vector: no no +Vector_Bounds: - - +Null_Allowed: yes yes + + +PARAMETER_TABLE: + + +Parameter_Name: noise_programmatic +Description: "enable programmatic noise sources" +Data_Type: boolean +Default_Value: TRUE +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: yes + +