Implement MIFnoise(), the generic noise callback for all XSPICE code

models. Two discovery modes are supported: declarative, where models
define reserved parameter names (noise_voltage, noise_current,
noise_corner, noise_exponent) and get automatic noise sources and
programmatic, where models register sources via cm_noise_add_source()
during MIF_NOI analysis calls.

Also add an OTA (operational transconductance amplifier) code model as a
reference implementation that exercises the programmatic noise interface.
This commit is contained in:
Seth Hillbrand 2026-03-22 23:15:52 +01:00 committed by Holger Vogt
parent 2d3e032a3f
commit 8841a3a658
24 changed files with 197 additions and 6 deletions

View File

@ -49,6 +49,7 @@ NON-STANDARD FEATURES
#define DC MIF_DC
#define AC MIF_AC
#define TRANSIENT MIF_TRAN
#define NOISE MIF_NOI
#define ANALOG MIF_ANALOG
#define EVENT MIF_EVENT_DRIVEN

View File

@ -112,6 +112,8 @@ Complex_t cm_complex_subtract(Complex_t x, Complex_t y);
Complex_t cm_complex_multiply(Complex_t x, Complex_t y);
Complex_t cm_complex_divide(Complex_t x, Complex_t y);
int cm_noise_add_source(const char *name, int conn_index, int port_index, Mif_Noise_Src_Type_t type);
char *cm_get_path(void);
CKTcircuit *cm_get_circuit(void);

View File

@ -92,6 +92,8 @@ struct coreInfo_t {
int ((*dllitf_MIFbindCSCComplex) (GENmodel *, CKTcircuit *)) ;
int ((*dllitf_MIFbindCSCComplexToReal) (GENmodel *, CKTcircuit *)) ;
#endif
int ((*dllitf_MIFnoise)(int, int, GENmodel *, CKTcircuit *, Ndata *, double *));
int ((*dllitf_cm_noise_add_source)(const char *, int, int, Mif_Noise_Src_Type_t));
};
#endif

View File

@ -82,6 +82,7 @@ extern int MIFiSize;
extern int MIFmSize;
extern Mif_Info_t g_mif_info;
extern Mif_Info_t g_mif_info;
extern Mif_Private_t g_mif_noise_cm_data;
#endif

View File

@ -386,6 +386,28 @@ typedef struct Mif_Inst_Var_Data_s {
/* ************************************************************************* */
/*
* Noise analysis context passed to code models via mif_private->noise.
* Stack-local in MIFnoise, valid only during cm_func call.
*/
typedef struct Mif_Noise_Data_s {
Mif_Boolean_t registering; /* TRUE during N_OPEN, FALSE during N_CALC */
int next_index; /* Monotonic counter, reset before each cm_func call */
int num_prog_srcs; /* Total programmatic sources after N_OPEN */
int max_prog_srcs; /* Allocated capacity of registration arrays */
Mif_Noise_Src_Type_t *prog_types; /* Source type per programmatic source */
int *prog_conn; /* Connection index per source */
int *prog_port; /* Port index per source */
char **prog_names; /* Source name suffix per source */
double *density; /* Spectral density array (N_CALC, sized num_prog_srcs) */
double freq; /* Current noise frequency in Hz */
} Mif_Noise_Data_t;
/* ************************************************************************* */
@ -405,6 +427,7 @@ struct Mif_Private {
int num_inst_var; /* Number of instance variables */
Mif_Inst_Var_Data_t **inst_var; /* Information about each inst variable */
Mif_Callback_t *callback; /* Callback function */
Mif_Noise_Data_t *noise; /* Noise context, NULL when not in noise analysis */
};

View File

@ -84,6 +84,17 @@ struct MIFinstance {
int inst_index; /* Index into inst_table in evt struct in ckt */
Mif_Callback_t callback; /* instance callback function */
int num_noise_srcs; /* Total noise sources (declarative + programmatic) */
Mif_Boolean_t noise_initialized; /* TRUE once sources discovered and allocated */
double *MIFnVar; /* Flat array [NSTATVARS * num_noise_srcs] */
int *noise_node1; /* Positive/branch node per source */
int *noise_node2; /* Negative/ground node per source */
char **noise_src_names; /* Suffix name per source */
int noise_decl_nv_base; /* Base index of voltage noise group, -1 if none */
int noise_decl_nc_base; /* Base index of current noise group, -1 if none */
int noise_prog_offset; /* Index where programmatic sources start */
double *noise_prog_density; /* Reusable density array for cm_func */
};

View File

@ -46,6 +46,7 @@ NON-STANDARD FEATURES
#include "ngspice/inpdefs.h"
#include "ngspice/smpdefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/noisedef.h"
#include "ngspice/miftypes.h"
@ -154,6 +155,17 @@ extern Mif_Cntl_Src_Type_t MIFget_cntl_src_type(
extern char *MIFcopy(char *);
extern int MIFnoise(
int mode,
int operation,
GENmodel *inModel,
CKTcircuit *ckt,
Ndata *data,
double *OnDens
);
extern void MIF_free_noise_state(MIFinstance *here);
#ifdef KLU
extern int MIFbindCSC (GENmodel*, CKTcircuit*) ;
extern int MIFbindCSCComplex (GENmodel*, CKTcircuit*) ;

View File

@ -232,4 +232,16 @@ typedef struct MIFmodel MIFmodel;
typedef void (* Mif_Callback_t)(Mif_Private_t *, Mif_Callback_Reason_t);
/*
* Noise source type for MIFnoise
*/
typedef enum {
MIF_NOISE_CURRENT, /* Current source between port nodes (pos_node, neg_node) */
MIF_NOISE_VOLTAGE, /* Voltage source at port branch equation (branch, 0) */
MIF_NOISE_CURRENT_POS, /* Current source from pos_node to ground */
MIF_NOISE_CURRENT_NEG, /* Current source from neg_node to ground */
} Mif_Noise_Src_Type_t;
#endif

View File

@ -336,8 +336,9 @@ NOISEan(CKTcircuit* ckt, int restart)
g_mif_info.circuit.anal_init = MIF_TRUE;
/* Tell the code models what mode we're in */
/* MIF_NOI is not yet supported by code models, so use their AC capabilities */
/* MIFnoise handles noise analysis for code models via DEVnoise.
* Set anal_type to MIF_AC so that MIFload generates correct AC matrix
* entries needed for gain computation during noise analysis. */
g_mif_info.circuit.anal_type = MIF_AC;
/* gtri - end - wbk */

View File

@ -926,3 +926,85 @@ bool cm_probe_node(unsigned int conn_index, // Connection index
this->output_value[edata->output_subindex] = hold;
return TRUE;
}
/*
cm_noise_add_source
Register a programmatic noise source during noise analysis.
During N_OPEN (registering=TRUE): validates parameters, stores source info
in the noise data registration arrays, and returns a sequential index.
During N_CALC (registering=FALSE): returns the same sequential index
without storing anything. The code model uses this index to set
NOISE_DENSITY(index).
*/
int
cm_noise_add_source(const char *name, int conn_index, int port_index,
Mif_Noise_Src_Type_t type)
{
Mif_Noise_Data_t *nd;
int idx;
nd = g_mif_noise_cm_data.noise;
if (!nd || !name)
return -1;
if (type != MIF_NOISE_CURRENT && type != MIF_NOISE_VOLTAGE &&
type != MIF_NOISE_CURRENT_POS && type != MIF_NOISE_CURRENT_NEG)
return -1;
idx = nd->next_index++;
if (!nd->registering)
return idx;
/* Registering mode: validate conn/port.
* Always store the source to keep indices aligned between N_OPEN and N_CALC.
* Invalid sources get conn_index = -1 so MIFnoise skips them during evaluation. */
{
MIFinstance *inst = g_mif_info.instance;
Mif_Boolean_t valid = MIF_TRUE;
if (conn_index < 0 || conn_index >= inst->num_conn ||
inst->conn[conn_index]->is_null ||
port_index < 0 || port_index >= inst->conn[conn_index]->size) {
fprintf(stderr, "cm_noise_add_source: invalid conn %d port %d for '%s'\n",
conn_index, port_index, name);
valid = MIF_FALSE;
}
if (!valid)
conn_index = -1;
}
/* Grow arrays if needed */
if (nd->num_prog_srcs >= nd->max_prog_srcs) {
int new_max = (nd->max_prog_srcs == 0) ? 8 : nd->max_prog_srcs * 2;
Mif_Noise_Src_Type_t *t = TREALLOC(Mif_Noise_Src_Type_t, nd->prog_types, new_max);
int *c = TREALLOC(int, nd->prog_conn, new_max);
int *p = TREALLOC(int, nd->prog_port, new_max);
char **n = TREALLOC(char *, nd->prog_names, new_max);
if (!t || !c || !p || !n)
return -1;
nd->prog_types = t;
nd->prog_conn = c;
nd->prog_port = p;
nd->prog_names = n;
nd->max_prog_srcs = new_max;
}
nd->prog_types[nd->num_prog_srcs] = type;
nd->prog_conn[nd->num_prog_srcs] = conn_index;
nd->prog_port[nd->num_prog_srcs] = port_index;
nd->prog_names[nd->num_prog_srcs] = tprintf("_%s", name);
nd->num_prog_srcs++;
return idx;
}

View File

@ -98,4 +98,7 @@ struct coreInfo_t coreInfo =
MIFbindCSCComplex,
MIFbindCSCComplexToReal
#endif
,
MIFnoise,
cm_noise_add_source
};

View File

@ -124,6 +124,8 @@ OUTPUT_STATE {return TOK_OUTPUT_STATE;}
OUTPUT_STRENGTH {return TOK_OUTPUT_STRENGTH;}
OUTPUT_TYPE {return TOK_OUTPUT_TYPE;}
OUTPUT_CHANGED {return TOK_OUTPUT_CHANGED;}
NOISE_DENSITY {return TOK_NOISE_DENSITY;}
NOISE_FREQ {return TOK_NOISE_FREQ;}
"(" {return TOK_LPAREN;}
")" {return TOK_RPAREN;}

View File

@ -369,6 +369,8 @@ static void append (char *str)
%token TOK_TOTAL_LOAD
%token TOK_MESSAGE
%token TOK_CALL_TYPE
%token TOK_NOISE_DENSITY
%token TOK_NOISE_FREQ
%start mod_file
@ -589,9 +591,14 @@ macro : TOK_INIT
subscript($3));}
| TOK_MESSAGE TOK_LPAREN subscriptable_id TOK_RPAREN
{int i = valid_subid ($3, CONN);
fprintf (mod_yyout,
fprintf (mod_yyout,
"mif_private->conn[%d]->port[%s]->msg", i,
subscript($3));}
| TOK_NOISE_DENSITY TOK_LPAREN buffered_c_code TOK_RPAREN
{fprintf (mod_yyout,
"mif_private->noise->density[%s]", $3);}
| TOK_NOISE_FREQ
{fprintf (mod_yyout, "mif_private->noise->freq");}
;
subscriptable_id : id

View File

@ -1106,7 +1106,7 @@ static int write_SPICEdev(
" .DEVsenPrint = NULL,\n"
" .DEVsenTrunc = NULL,\n"
" .DEVdisto = NULL,\n"
" .DEVnoise = NULL,\n"
" .DEVnoise = MIFnoise,\n"
" .DEVsoaCheck = NULL,\n"
" .DEVinstSize = &val_sizeofMIFinstance,\n"
" .DEVmodSize = &val_sizeofMIFmodel,\n"

View File

@ -21,3 +21,4 @@ file_source
delay
pwlts
astate
ota

View File

@ -18,6 +18,7 @@
#include "ngspice/devdefs.h"
#include "ngspice/dstring.h"
#include "ngspice/dllitf.h"
#include "ngspice/noisedef.h"
#include "ngspice/evtudn.h"
#include "ngspice/inpdefs.h"
#include "ngspice/inertial.h"
@ -157,10 +158,21 @@ int MIFload(
}
int MIFnoise(
int mode,
int operation,
GENmodel *inModel,
CKTcircuit *ckt,
Ndata *data,
double *OnDens
) {
return (coreitf->dllitf_MIFnoise)(mode,operation,inModel,ckt,data,OnDens);
}
int MIFmParam(
int param_index,
IFvalue *value,
GENmodel *inModel
GENmodel *inModel
) {
return (coreitf->dllitf_MIFmParam)(param_index,value,inModel);
}
@ -444,6 +456,10 @@ void cm_cexit(const int exitcode) {
(coreitf->dllitf_cexit)(exitcode);
}
int cm_noise_add_source(const char *name, int conn_index, int port_index, Mif_Noise_Src_Type_t type) {
return (coreitf->dllitf_cm_noise_add_source)(name, conn_index, port_index, type);
}
#ifdef KLU
int MIFbindCSC (GENmodel *inModel, CKTcircuit *ckt) {
return (coreitf->dllitf_MIFbindCSC) (inModel, ckt) ;

View File

@ -19,6 +19,7 @@ libmifxsp_la_SOURCES = \
mifdelete.c \
mifmdelete.c \
mifdestr.c \
mifnoise.c \
mif.c
if KLU_WANTED

View File

@ -57,3 +57,7 @@ Mif_Info_t g_mif_info = {
{ 0.0, 0.0,},
{ MIF_FALSE, MIF_FALSE,},
};
/* Mif_Private_t used by MIFnoise to call cm_func during noise analysis.
* Also accessed by cm_noise_add_source() in cm.c. */
Mif_Private_t g_mif_noise_cm_data;

View File

@ -194,5 +194,7 @@ MIFdelete(GENinstance *gen_inst)
if (here->num_conv && here->conv)
FREE(here->conv);
MIF_free_noise_state(here);
return OK;
}

View File

@ -574,6 +574,8 @@ MIFunsetup(GENmodel *inModel,CKTcircuit *ckt)
here->callback(&cm_data, MIF_CB_DESTROY);
}
MIF_free_noise_state(here);
here->initialized = MIF_FALSE;
} /* end for all instances */
}

View File

@ -89,4 +89,7 @@ struct coreInfo_t coreInfo =
MIFbindCSCComplex,
MIFbindCSCComplexToReal
#endif
,
MIFnoise,
cm_noise_add_source
};

View File

@ -2620,6 +2620,7 @@ lib /machine:x64 /def:..\..\fftw-3.3-dll64\libfftw3-3.def /out:$(IntDir)libfftw3
<ClCompile Include="..\src\xspice\mif\mifgetvalue.c" />
<ClCompile Include="..\src\xspice\mif\mifload.c" />
<ClCompile Include="..\src\xspice\mif\mifmask.c" />
<ClCompile Include="..\src\xspice\mif\mifnoise.c" />
<ClCompile Include="..\src\xspice\mif\mifmdelete.c" />
<ClCompile Include="..\src\xspice\mif\mifmpara.c" />
<ClCompile Include="..\src\xspice\mif\mifsetup.c" />

View File

@ -2840,6 +2840,7 @@ lib /machine:x64 /def:..\..\fftw-3.3-dll64\libfftw3-3.def /out:$(IntDir)libfftw3
<ClCompile Include="..\src\xspice\mif\mifgetvalue.c" />
<ClCompile Include="..\src\xspice\mif\mifload.c" />
<ClCompile Include="..\src\xspice\mif\mifmask.c" />
<ClCompile Include="..\src\xspice\mif\mifnoise.c" />
<ClCompile Include="..\src\xspice\mif\mifmdelete.c" />
<ClCompile Include="..\src\xspice\mif\mifmpara.c" />
<ClCompile Include="..\src\xspice\mif\mifsetup.c" />

View File

@ -2855,6 +2855,7 @@
<ClCompile Include="..\src\xspice\mif\mifgetvalue.c" />
<ClCompile Include="..\src\xspice\mif\mifload.c" />
<ClCompile Include="..\src\xspice\mif\mifmask.c" />
<ClCompile Include="..\src\xspice\mif\mifnoise.c" />
<ClCompile Include="..\src\xspice\mif\mifmdelete.c" />
<ClCompile Include="..\src\xspice\mif\mifmpara.c" />
<ClCompile Include="..\src\xspice\mif\mifsetup.c" />