From c44f22b116e004be6c53adba52a0d7268884c3d2 Mon Sep 17 00:00:00 2001 From: DSPOM Date: Wed, 20 Apr 2022 18:12:10 +0200 Subject: [PATCH] feat: OSDI integration prototype This initial prototype is capable of performing DC, transient and AC analysis. Not all features of OSDI are supported yet and there are still some open questions regarding ngspice integration. However, simple example like a level 0 diode are working well test: test case for diode DC working with SH test: add transient analysis to osdi_diode test test: added docu text to osdi_diode test test: added test case directories fix: bug in osdi_load test: small change to netlist fix: implement DEVunsetup fix: correct behaviour for MODEINITSMSIG test: osdi diode enable all analysis modes --- configure.ac | 1 + src/Makefile.am | 1 + src/frontend/gens.c | 2 +- src/include/ngspice/devdefs.h | 5 +- src/include/ngspice/inpdefs.h | 2 + src/include/ngspice/ngspice.h | 1 + src/misc/ivars.c | 3 + src/spicelib/devices/Makefile.am | 2 + src/spicelib/devices/dev.c | 16 +- src/spicelib/devices/osdi/Makefile.am | 22 ++ src/spicelib/devices/osdi/osdi.h | 155 ++++++++++ src/spicelib/devices/osdi/osdiacld.c | 35 +++ src/spicelib/devices/osdi/osdidefs.h | 80 +++++ src/spicelib/devices/osdi/osdiext.h | 29 ++ src/spicelib/devices/osdi/osdiinit.c | 181 +++++++++++ src/spicelib/devices/osdi/osdiitf.h | 18 ++ src/spicelib/devices/osdi/osdiload.c | 162 ++++++++++ src/spicelib/devices/osdi/osdiparam.c | 159 ++++++++++ src/spicelib/devices/osdi/osdiregistry.c | 243 +++++++++++++++ src/spicelib/devices/osdi/osdisetup.c | 365 +++++++++++++++++++++++ src/spicelib/parser/Makefile.am | 1 + src/spicelib/parser/inp2a.c | 99 ++++++ src/spicelib/parser/inpdomod.c | 19 ++ src/spicelib/parser/inpfindl.c | 39 +++ src/spicelib/parser/inpgmod.c | 12 + src/spicelib/parser/inppas2.c | 6 + src/spicelib/parser/inptyplk.c | 15 +- src/spicelib/parser/inpxx.h | 1 + test_cases/capacitor/.empty.txt | 0 test_cases/cccs/.empty.txt | 0 test_cases/ccvs/.empty.txt | 0 test_cases/diode/netlist.sp | 44 +++ test_cases/diode/test_diode.py | 205 +++++++++++++ test_cases/diode/test_osdi/netlist.sp | 44 +++ test_cases/hicuml2/.empty.txt | 0 test_cases/inductor/.empty.txt | 0 test_cases/multiple_devices/.empty.txt | 0 test_cases/node_collapsing/.empty.txt | 0 test_cases/resistor/.empty.txt | 0 test_cases/vccs/.empty.txt | 0 test_cases/vcvs/.empty.txt | 0 41 files changed, 1962 insertions(+), 5 deletions(-) create mode 100644 src/spicelib/devices/osdi/Makefile.am create mode 100644 src/spicelib/devices/osdi/osdi.h create mode 100644 src/spicelib/devices/osdi/osdiacld.c create mode 100644 src/spicelib/devices/osdi/osdidefs.h create mode 100644 src/spicelib/devices/osdi/osdiext.h create mode 100644 src/spicelib/devices/osdi/osdiinit.c create mode 100644 src/spicelib/devices/osdi/osdiitf.h create mode 100644 src/spicelib/devices/osdi/osdiload.c create mode 100644 src/spicelib/devices/osdi/osdiparam.c create mode 100644 src/spicelib/devices/osdi/osdiregistry.c create mode 100644 src/spicelib/devices/osdi/osdisetup.c create mode 100644 src/spicelib/parser/inp2a.c create mode 100644 test_cases/capacitor/.empty.txt create mode 100644 test_cases/cccs/.empty.txt create mode 100644 test_cases/ccvs/.empty.txt create mode 100644 test_cases/diode/netlist.sp create mode 100644 test_cases/diode/test_diode.py create mode 100644 test_cases/diode/test_osdi/netlist.sp create mode 100644 test_cases/hicuml2/.empty.txt create mode 100644 test_cases/inductor/.empty.txt create mode 100644 test_cases/multiple_devices/.empty.txt create mode 100644 test_cases/node_collapsing/.empty.txt create mode 100644 test_cases/resistor/.empty.txt create mode 100644 test_cases/vccs/.empty.txt create mode 100644 test_cases/vcvs/.empty.txt diff --git a/configure.ac b/configure.ac index 5a0339f1c..039ccd01d 100644 --- a/configure.ac +++ b/configure.ac @@ -1373,6 +1373,7 @@ AC_CONFIG_FILES([Makefile src/spicelib/devices/hfet1/Makefile src/spicelib/devices/hfet2/Makefile src/spicelib/devices/hicum2/Makefile + src/spicelib/devices/osdi/Makefile src/spicelib/devices/hisim2/Makefile src/spicelib/devices/hisimhv1/Makefile src/spicelib/devices/hisimhv2/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 1f0e53588..f125ba2ae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -70,6 +70,7 @@ DYNAMIC_DEVICELIBS = \ spicelib/devices/hfet1/libhfet.la \ spicelib/devices/hfet2/libhfet2.la \ spicelib/devices/hicum2/libhicum2.la \ + spicelib/devices/osdi/libosdi.la \ spicelib/devices/hisim2/libhisim2.la \ spicelib/devices/hisimhv1/libhisimhv1.la \ spicelib/devices/hisimhv2/libhisimhv2.la \ diff --git a/src/frontend/gens.c b/src/frontend/gens.c index d9df16a3a..bddd8dcc5 100644 --- a/src/frontend/gens.c +++ b/src/frontend/gens.c @@ -163,7 +163,7 @@ dgen_next(dgen **dgx) if (!dg->dev_list) { if ((dg->flags & DGEN_ALLDEVS) || ((dg->flags & DGEN_DEFDEVS) && - (ft_sim->devices[dg->dev_type_no]->flags & DEV_DEFAULT))) + (ft_sim->devices[dg->dev_type_no]->flags & DEV_DEFAULT_CHECK))) { done = 1; } else { diff --git a/src/include/ngspice/devdefs.h b/src/include/ngspice/devdefs.h index 832aa4751..3fc0451ce 100644 --- a/src/include/ngspice/devdefs.h +++ b/src/include/ngspice/devdefs.h @@ -183,6 +183,9 @@ extern int DEVmaxnum; /* size of DEVices array */ -#define DEV_DEFAULT 0x1 +#define DEV_STATIC 0x1 +#define DEV_DEFAULT_CHECK 0x2 +#define DEV_DEFAULT (DEV_DEFAULT_CHECK | DEV_STATIC) +#define DEV_OSDI 0x4 #endif diff --git a/src/include/ngspice/inpdefs.h b/src/include/ngspice/inpdefs.h index 49488ea88..74c305a59 100644 --- a/src/include/ngspice/inpdefs.h +++ b/src/include/ngspice/inpdefs.h @@ -120,6 +120,7 @@ double INPevaluateRKM_R(char **, int *, int); double INPevaluateRKM_C(char **, int *, int); double INPevaluateRKM_L(char **, int *, int); char *INPfindLev(char *, int *); +char *INPfindModule(char *); char *INPgetMod(CKTcircuit *, char *, INPmodel **, INPtables *); char *INPgetModBin(CKTcircuit *, char *, INPmodel **, INPtables *, char *); int INPgetTok(char **, char **, int); @@ -145,6 +146,7 @@ int INPtermInsert(CKTcircuit *, char **, INPtables *, CKTnode **); int INPtermSearch(CKTcircuit*, char**, INPtables*, CKTnode**); int INPmkTerm(CKTcircuit *, char **, INPtables *, CKTnode **); int INPtypelook(char *); +int INPtypelook_flag(char *, int); int INP2dot(CKTcircuit *, INPtables *, struct card *, TSKtask *, CKTnode *); INPtables *INPtabInit(int); void INPkillMods(void); diff --git a/src/include/ngspice/ngspice.h b/src/include/ngspice/ngspice.h index 74535553e..0a80c2b30 100644 --- a/src/include/ngspice/ngspice.h +++ b/src/include/ngspice/ngspice.h @@ -279,6 +279,7 @@ extern char *Help_Path; extern char *Lib_Path; extern char *Inp_Path; extern char *Infile_Path; +extern char *OSDI_Path; #ifdef TCL_MODULE diff --git a/src/misc/ivars.c b/src/misc/ivars.c index 5c8afcaa9..fed992bb9 100644 --- a/src/misc/ivars.c +++ b/src/misc/ivars.c @@ -72,6 +72,8 @@ ivars(char *argv0) mkvar(&Help_Path, Spice_Lib_Dir, "helpdir", "SPICE_HELP_DIR"); /* where spinit is found */ mkvar(&Lib_Path, Spice_Lib_Dir, "scripts", "SPICE_SCRIPTS"); + /* used to search for OSDI models*/ + mkvar(&OSDI_Path, Spice_Lib_Dir, "osdi", "SPICE_OSDI"); /* used to call ngspice with aspice command, not used in Windows mode */ mkvar(&Spice_Path, Spice_Exec_Dir, "ngspice", "SPICE_PATH"); tfree(temp); @@ -113,4 +115,5 @@ destroy_ivars(void) tfree(Lib_Path); tfree(Spice_Path); tfree(Inp_Path); + tfree(OSDI_Path); } diff --git a/src/spicelib/devices/Makefile.am b/src/spicelib/devices/Makefile.am index bc6674611..80dc9830f 100644 --- a/src/spicelib/devices/Makefile.am +++ b/src/spicelib/devices/Makefile.am @@ -28,6 +28,7 @@ SUBDIRS = \ hfet1 \ hfet2 \ hicum2 \ + osdi \ hisim2 \ hisimhv1 \ hisimhv2 \ @@ -63,6 +64,7 @@ SUBDIRS += nbjt nbjt2 numd numd2 numos endif DIST_SUBDIRS = \ + osdi \ asrc \ bjt \ bsim1 \ diff --git a/src/spicelib/devices/dev.c b/src/spicelib/devices/dev.c index 43b5180c9..e50984737 100644 --- a/src/spicelib/devices/dev.c +++ b/src/spicelib/devices/dev.c @@ -30,12 +30,15 @@ #include "ngspice/ngspice.h" #include "assert.h" +#include "ngspice/iferrmsg.h" #include "ngspice/devdefs.h" #include "ngspice/ifsim.h" #include "dev.h" #include "ngspice/memory.h" /* to alloc, realloc devices*/ +#include "osdi/osdiitf.h" + #ifdef XSPICE /*saj headers for xspice*/ @@ -276,11 +279,22 @@ spice_init_devices(void) DEVicesfl = TMALLOC(int, DEVNUM); /* tmalloc should automatically zero the array! */ #endif + // load the object files and allocate additional space for OSDI devices + // this could also be done later with realloc + int num_osdi_devices = osdi_load_devices(); + if (num_osdi_devices < 0) { + // TODO error handeling + printf("FATAL error during osdi device load\n %s\n", errMsg); + exit(1); + } - DEVices = TMALLOC(SPICEdev *, DEVNUM); + DEVices = TMALLOC(SPICEdev *, DEVNUM + num_osdi_devices); for (i = 0; i < DEVNUM; i++) DEVices[i] = static_devices[i](); + + osdi_get_info((uint32_t)DEVNUM, (uint32_t)num_osdi_devices); + DEVNUM += num_osdi_devices; } int num_devices(void) diff --git a/src/spicelib/devices/osdi/Makefile.am b/src/spicelib/devices/osdi/Makefile.am new file mode 100644 index 000000000..2789ccf7d --- /dev/null +++ b/src/spicelib/devices/osdi/Makefile.am @@ -0,0 +1,22 @@ +## Process this file with automake to produce Makefile.in + +noinst_LTLIBRARIES = libosdi.la + +libosdi_la_SOURCES = \ + osdi.h \ + osdidefs.h \ + osdiext.h \ + osdiinit.c \ + osdiload.c \ + osdiacld.c \ + osdiparam.c \ + osdiregistry.c \ + osdisetup.c \ + osdiitf.h + + + + +AM_CPPFLAGS = @AM_CPPFLAGS@ -I$(top_srcdir)/src/include +AM_CFLAGS = $(STATIC) +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/spicelib/devices/osdi/osdi.h b/src/spicelib/devices/osdi/osdi.h new file mode 100644 index 000000000..d0bc6baa4 --- /dev/null +++ b/src/spicelib/devices/osdi/osdi.h @@ -0,0 +1,155 @@ +/* + * This file is automatically generated by header.lua DO NOT EDIT MANUALLY + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#pragma once +#include +#include +#include + + +#define OSDI_VERSION_MAJOR_CURR 0 +#define OSDI_VERSION_MINOR_CURR 2 + +#define PARA_TY_MASK 3 +#define PARA_TY_REAL 0 +#define PARA_TY_INT 1 +#define PARA_TY_STR 2 +#define PARA_KIND_MASK (3 << 30) +#define PARA_KIND_MODEL (0 << 30) +#define PARA_KIND_INST (1 << 30) +#define PARA_KIND_OPVAR (2 << 30) + +#define ACCESS_FLAG_READ 0 +#define ACCESS_FLAG_SET 1 +#define ACCESS_FLAG_INSTANCE 4 + +#define CALC_RESIST_RESIDUAL 1 +#define CALC_REACT_RESIDUAL 2 +#define CALC_RESIST_JACOBIAN 4 +#define CALC_REACT_JACOBIAN 8 +#define CALC_NOISE 16 +#define CALC_OP 32 + +#define EVAL_RET_FLAG_LIM 1 +#define EVAL_RET_FLAG_FATAL 2 +#define EVAL_RET_FLAG_FINISH 4 +#define EVAL_RET_FLAG_STOP 8 + +#define LOG_LVL_DEBUG 0 +#define LOG_LVL_INFO 1 +#define LOG_LVL_WARN 2 +#define LOG_LVL_ERR 3 +#define LOG_LVL_FATAL 4 + +#define INIT_ERR_OUT_OF_BOUNDS 1 + + + +typedef struct OsdiSimParas { + char **names; + double *vals; + char **names_str; + char **vals_str; +}OsdiSimParas; + +typedef union OsdiInitErrorPayload { + uint32_t parameter_id; +}OsdiInitErrorPayload; + +typedef struct OsdiInitError { + uint32_t code; + OsdiInitErrorPayload payload; +}OsdiInitError; + +typedef struct OsdiInitInfo { + uint32_t flags; + uint32_t num_errors; + OsdiInitError *errors; +}OsdiInitInfo; + +typedef struct OsdiNodePair { + uint32_t node_1; + uint32_t node_2; +}OsdiNodePair; + +typedef struct OsdiNode { + char *name; + bool is_reactive; + char *units; + char *residual_units; +}OsdiNode; + +typedef struct OsdiParamOpvar { + char **name; + uint32_t num_alias; + char *description; + char *units; + uint32_t flags; + uint32_t len; +}OsdiParamOpvar; + +typedef struct OsdiNoiseSource { + char *name; + OsdiNodePair nodes; +}OsdiNoiseSource; + +typedef struct OsdiDescriptor { + char *name; + + uint32_t num_nodes; + uint32_t num_terminals; + OsdiNode *nodes; + + uint32_t num_jacobian_entries; + OsdiNodePair *jacobian_entries; + bool *const_jacobian_entries; + + uint32_t num_collapsible; + OsdiNodePair *collapsible; + size_t is_collapsible_offset; + + OsdiNoiseSource *noise_sources; + uint32_t num_noise_src; + + uint32_t num_params; + uint32_t num_instance_params; + uint32_t num_opvars; + OsdiParamOpvar *param_opvar; + + size_t residual_resist_offset; + size_t residual_react_offset; + size_t jacobian_resist_offset; + size_t jacobian_react_offset; + size_t node_mapping_offset; + size_t jacobian_ptr_resist_offset; + size_t jacobian_ptr_react_offset; + + size_t instance_size; + size_t model_size; + + void *(*access)(void *inst, void *model, uint32_t id, uint32_t flags); + + OsdiInitInfo (*setup_model)(void *handle, void *model); + OsdiInitInfo (*setup_instance)(void *handle, void *inst, void *model, + double temperature, uint32_t num_terminals); + + uint32_t (*eval)(void *handle, void *inst, void *model, uint32_t flags, + double *prev_solve, OsdiSimParas *sim_params); + void (*load_noise)(void *inst, void *model, double freq, double *noise_dens, + double *ln_noise_dens); + void (*load_residual_resist)(void *inst, double *dst); + void (*load_residual_react)(void *inst, double *dst); + void (*load_spice_rhs_dc)(void *inst, double *dst, double* prev_solve); + void (*load_spice_rhs_tran)(void *inst, double *dst, double* prev_solve, double alpha); + void (*load_jacobian_resist)(void *inst); + void (*load_jacobian_react)(void *inst, double alpha); + void (*load_jacobian_tran)(void *inst, double alpha); +}OsdiDescriptor; + + + +extern FILE *osdi_init_log_message(void *handle, uint32_t lvl); +extern void osdi_finish_log_message(void *handle, FILE *stream, uint32_t lvl); + diff --git a/src/spicelib/devices/osdi/osdiacld.c b/src/spicelib/devices/osdi/osdiacld.c new file mode 100644 index 000000000..a311d9dfa --- /dev/null +++ b/src/spicelib/devices/osdi/osdiacld.c @@ -0,0 +1,35 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#include "ngspice/iferrmsg.h" +#include "ngspice/memory.h" +#include "ngspice/ngspice.h" +#include "ngspice/typedefs.h" + +#include "osdi.h" +#include "osdidefs.h" + +#include +#include +#include + +int OSDIacLoad(GENmodel *inModel, CKTcircuit *ckt) { + + GENmodel *gen_model; + GENinstance *gen_inst; + + OsdiRegistryEntry entry = osdi_reg_entry_model(inModel); + const OsdiDescriptor *descr = entry.descriptor; + for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) { + for (gen_inst = gen_model->GENinstances; gen_inst; + gen_inst = gen_inst->GENnextInstance) { + void *inst = osdi_instance_data(entry, gen_inst); + // nothing to calculate just load the matrix entries calculated during + // operating point iterations + descr->load_jacobian_resist(inst); + descr->load_jacobian_react(inst, ckt->CKTomega); + } + } + return (OK); +} diff --git a/src/spicelib/devices/osdi/osdidefs.h b/src/spicelib/devices/osdi/osdidefs.h new file mode 100644 index 000000000..af078667b --- /dev/null +++ b/src/spicelib/devices/osdi/osdidefs.h @@ -0,0 +1,80 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#pragma once + +#include "ngspice/cktdefs.h" +#include "ngspice/complex.h" +#include "ngspice/gendefs.h" +#include "ngspice/ifsim.h" +#include "ngspice/ngspice.h" +#include "ngspice/noisedef.h" +#include "ngspice/typedefs.h" + +#include "osdi.h" +#include "osdiext.h" + +#include "stddef.h" +#include +#include + +typedef struct OsdiModelData { + GENmodel gen; + max_align_t data; +} OsdiModelData; + +typedef struct OsdiExtraInstData { + double dt; + double temp; + bool temp_given; + bool dt_given; + bool lim; + bool finish; + +} __attribute__((aligned(sizeof(max_align_t)))) OsdiExtraInstData; + +typedef struct OsdiRegistryEntry { + const OsdiDescriptor *descriptor; + uint32_t inst_offset; + uint32_t dt; + uint32_t temp; +} OsdiRegistryEntry; + +extern OsdiRegistryEntry *registry; +extern uint32_t registry_off; + +inline size_t osdi_instance_data_off(OsdiRegistryEntry entry) { + return entry.inst_offset; +} +inline void *osdi_instance_data(OsdiRegistryEntry entry, GENinstance *inst) { + return (void *)(((char *)inst) + osdi_instance_data_off(entry)); +} +inline OsdiExtraInstData *osdi_extra_instance_data(OsdiRegistryEntry entry, + GENinstance *inst) { + return (OsdiExtraInstData *)(((char *)inst) + entry.inst_offset + + entry.descriptor->instance_size); +} + +inline size_t osdi_model_data_off() { return offsetof(OsdiModelData, data); } + +inline void *osdi_model_data(GENmodel *model) { + return (void *)&((OsdiModelData *)model)->data; +} + +inline void *osdi_model_data_from_inst(GENinstance *inst) { + return osdi_model_data(inst->GENmodPtr); +} + +inline OsdiRegistryEntry osdi_reg_entry_model(GENmodel *model) { + return registry[(uint32_t)model->GENmodType - registry_off]; +} + +inline OsdiRegistryEntry osdi_reg_entry_inst(GENinstance *inst) { + return osdi_reg_entry_model(inst->GENmodPtr); +} + +typedef struct OsdiNgspiceHandle { + uint32_t kind; + char *name; +} OsdiNgspiceHandle; diff --git a/src/spicelib/devices/osdi/osdiext.h b/src/spicelib/devices/osdi/osdiext.h new file mode 100644 index 000000000..dd4d035b7 --- /dev/null +++ b/src/spicelib/devices/osdi/osdiext.h @@ -0,0 +1,29 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#pragma once + +#include "ngspice/gendefs.h" +#include "ngspice/smpdefs.h" +#include + +#include "osdiitf.h" + +extern int OSDImParam(int, IFvalue *, GENmodel *); +extern int OSDIparam(int, IFvalue *, GENinstance *, IFvalue *); +extern int OSDIsetup(SMPmatrix *, GENmodel *, CKTcircuit *, int *); +extern int OSDIunsetup(GENmodel *, CKTcircuit *); +extern int OSDIask(CKTcircuit *, GENinstance *, int, IFvalue *, IFvalue *); +extern int OSDIload(GENmodel *, CKTcircuit *); +extern int OSDItemp(GENmodel *, CKTcircuit *); +extern int OSDIacLoad(GENmodel *,CKTcircuit*); +/* extern int OSDIconvTest(GENmodel*,CKTcircuit*); */ +/* extern int OSDImDelete(GENmodel*); */ +/* extern int OSDIgetic(GENmodel*,CKTcircuit*); */ +/* extern int OSDImAsk(CKTcircuit*,GENmodel*,int,IFvalue*); */ +/* extern int OSDIpzLoad(GENmodel*, CKTcircuit*, SPcomplex*); */ +/* extern int OSDIunsetup(GENmodel*,CKTcircuit*); */ +/* extern int OSDItrunc(GENmodel*,CKTcircuit*,double*); */ +/* extern int OSDInoise(int,int,GENmodel*,CKTcircuit*,Ndata*,double*); */ +/* extern int OSDIsoaCheck(CKTcircuit *, GENmodel *); */ diff --git a/src/spicelib/devices/osdi/osdiinit.c b/src/spicelib/devices/osdi/osdiinit.c new file mode 100644 index 000000000..cd8938eb5 --- /dev/null +++ b/src/spicelib/devices/osdi/osdiinit.c @@ -0,0 +1,181 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#include "ngspice/stringutil.h" + +#include "ngspice/config.h" +#include "ngspice/devdefs.h" +#include "ngspice/iferrmsg.h" +#include "ngspice/memory.h" +#include "ngspice/ngspice.h" +#include "ngspice/typedefs.h" + +#include "../dev.h" + +#include "osdidefs.h" + +#include +#include +#include + +/** + * This function converts the information in (a list of) OsdiParamOpvar in + * descr->param_opvar to the internal ngspice representation (IFparm). + * */ +static int write_param_info(IFparm **dst, const OsdiDescriptor *descr, + uint32_t start, uint32_t end) { + for (uint32_t i = start; i < end; i++) { + OsdiParamOpvar *para = &descr->param_opvar[i]; + uint32_t num_names = para->num_alias + 1; + + int dataType = IF_ASK; + if ((para->flags & (uint32_t)PARA_KIND_OPVAR) == 0) { + dataType |= IF_SET; + } + + switch (para->flags & PARA_TY_MASK) { + case PARA_TY_REAL: + dataType |= IF_REAL; + break; + case PARA_TY_INT: + dataType |= IF_INTEGER; + break; + case PARA_TY_STR: + dataType |= IF_STRING; + break; + default: + errRtn = "get_osdi_info"; + errMsg = tprintf("Unkown OSDI type %d for parameter %s!", + para->flags & PARA_TY_MASK, para->name[0]); + return -1; + } + + if (para->len != 0) { + dataType |= IF_VECTOR; + } + + for (uint32_t j = 0; j < num_names; j++) { + if (j != 0) { + dataType = IF_UNINTERESTING; + } + char *para_name = copy(para->name[j]); + strtolower(para_name); + (*dst)[j] = (IFparm){.keyword = para_name, + .id = (int)i, + .description = para->description, + .dataType = dataType}; + } + *dst += num_names; + } + + return 0; +} +/** + * This function creates a SPICEdev instance for a specific OsdiDescriptor by + * populating the SPICEdev struct with descriptor specific metadata and pointers + * to the descriptor independent functions. + * */ +static SPICEdev *osdi_init_descr(uint32_t id) { + const OsdiDescriptor *descr = registry[id].descriptor; + + // allocate and fill terminal names array + char **termNames = TMALLOC(char *, descr->num_terminals); + for (uint32_t i = 0; i < descr->num_terminals; i++) { + termNames[i] = descr->nodes[i].name; + } + + // allocate and fill instance params (and opvars) + int *num_instance_para_names = TMALLOC(int, 1); + for (uint32_t i = 0; i < descr->num_instance_params; i++) { + *num_instance_para_names += 1 + descr->param_opvar[i].num_alias; + } + for (uint32_t i = descr->num_params; + i < descr->num_opvars + descr->num_params; i++) { + *num_instance_para_names += 1 + descr->param_opvar[i].num_alias; + } + if (registry[id].dt != UINT32_MAX) { + *num_instance_para_names += 1; + } + + if (registry[id].temp != UINT32_MAX) { + *num_instance_para_names += 1; + } + + IFparm *instance_para_names = TMALLOC(IFparm, *num_instance_para_names); + IFparm *dst = instance_para_names; + + if (registry[id].dt != UINT32_MAX) { + dst[0] = (IFparm){"dt", (int)registry[id].dt, IF_REAL | IF_SET, + "Instance delta temperature"}; + dst += 1; + } + + if (registry[id].temp != UINT32_MAX) { + dst[0] = (IFparm){"temp", (int)registry[id].temp, IF_REAL | IF_SET, + "Instance temperature"}; + dst += 1; + } + write_param_info(&dst, descr, 0, descr->num_instance_params); + write_param_info(&dst, descr, descr->num_params, + descr->num_params + descr->num_opvars); + + // allocate and fill model params + int *num_model_para_names = TMALLOC(int, 1); + for (uint32_t i = descr->num_instance_params; i < descr->num_params; i++) { + *num_model_para_names += 1 + descr->param_opvar[i].num_alias; + } + IFparm *model_para_names = TMALLOC(IFparm, *num_model_para_names); + dst = model_para_names; + write_param_info(&dst, descr, descr->num_instance_params, descr->num_params); + + // Allocate SPICE device + SPICEdev *OSDIinfo = TMALLOC(SPICEdev, 1); + + // fill information + OSDIinfo->DEVpublic = (IFdevice){ + .name = descr->name, + .description = "A simulator independent device loaded with OSDI", + // TODO why extra indirection? Optional ports? + .terms = (int *)&descr->num_terminals, + .numNames = (int *)&descr->num_terminals, + .termNames = termNames, + .numInstanceParms = num_instance_para_names, + .instanceParms = instance_para_names, + .numModelParms = num_model_para_names, + .modelParms = model_para_names, + .flags = DEV_DEFAULT_CHECK | DEV_OSDI, + }; + + size_t inst_off = registry[id].inst_offset; + + int *inst_size = TMALLOC(int, 1); + *inst_size = + (int)(inst_off + descr->instance_size + sizeof(OsdiExtraInstData)); + OSDIinfo->DEVinstSize = inst_size; + + size_t model_off = osdi_model_data_off(); + int *model_size = TMALLOC(int, 1); + *model_size = (int)(model_off + descr->model_size); + OSDIinfo->DEVmodSize = model_size; + + // fill generic functions + OSDIinfo->DEVparam = OSDIparam; + OSDIinfo->DEVmodParam = OSDImParam; + OSDIinfo->DEVsetup = OSDIsetup; + OSDIinfo->DEVask = OSDIask; + OSDIinfo->DEVtemperature = OSDItemp; + OSDIinfo->DEVload = OSDIload; + OSDIinfo->DEVacLoad = OSDIacLoad; + OSDIinfo->DEVunsetup = OSDIunsetup; + + return OSDIinfo; +} + +extern void osdi_get_info(uint32_t off, uint32_t num_descriptors) { + SPICEdev **dst = devices() + off; + registry_off = off; + for (uint32_t i = 0; i < num_descriptors; i++) { + dst[i] = osdi_init_descr(i); + } +} diff --git a/src/spicelib/devices/osdi/osdiitf.h b/src/spicelib/devices/osdi/osdiitf.h new file mode 100644 index 000000000..86a187c5f --- /dev/null +++ b/src/spicelib/devices/osdi/osdiitf.h @@ -0,0 +1,18 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#pragma once + +#include + +/** + * Loads a list of object files into the global OSDI registry. + * This function must only be called once at program startup. + * + * @param PATH *object_files The paths of the object files + * @param uint32_t num_files The number of object files to load + * @returns The number of loaded descriptors + */ +extern int osdi_load_devices(void); +extern void osdi_get_info(uint32_t off, uint32_t num_descriptors); diff --git a/src/spicelib/devices/osdi/osdiload.c b/src/spicelib/devices/osdi/osdiload.c new file mode 100644 index 000000000..986319fa0 --- /dev/null +++ b/src/spicelib/devices/osdi/osdiload.c @@ -0,0 +1,162 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#include "ngspice/iferrmsg.h" +#include "ngspice/memory.h" +#include "ngspice/ngspice.h" +#include "ngspice/typedefs.h" + +#include "osdi.h" +#include "osdidefs.h" + +#include +#include +#include + +#define NUM_SIM_PARAMS 4 +char *sim_params[NUM_SIM_PARAMS + 1] = {"gdev", "gmin", "tnom", + "simulatorVersion", NULL}; +char *sim_params_str[1] = {NULL}; + +extern int OSDIload(GENmodel *inModel, CKTcircuit *ckt) { + OsdiNgspiceHandle handle; + GENmodel *gen_model; + GENinstance *gen_inst; + double dump; + + bool is_init_smsig = ckt->CKTmode & MODEINITSMSIG; + bool is_ac = ckt->CKTmode & (MODEAC | MODEINITSMSIG); + bool is_tran_op = ((ckt->CKTmode & MODETRANOP) && (ckt->CKTmode & MODEUIC)); + bool is_tran = ckt->CKTmode & (MODEDCTRANCURVE | MODETRAN) || is_tran_op; + bool is_init_tran = ckt->CKTmode & MODEINITTRAN; + + uint32_t flags = CALC_RESIST_JACOBIAN; + + if (is_init_smsig) { + flags |= CALC_OP; + } else { + flags |= CALC_RESIST_RESIDUAL; + } + + if (is_tran || is_ac || is_tran_op) { + flags |= CALC_REACT_JACOBIAN; + } + + if (is_tran || is_tran_op) { + flags |= CALC_REACT_RESIDUAL; + } + + if (ckt->CKTmode & MODEACNOISE) { + flags |= CALC_NOISE; + } + + int ret = OK; + + /* values returned by $simparam*/ + double simulatorVersion = strtod(PACKAGE_VERSION, NULL); + double gdev = ckt->CKTgmin; + double gmin = ((ckt->CKTgmin) > (ckt->CKTdiagGmin)) ? (ckt->CKTgmin) + : (ckt->CKTdiagGmin); + double sim_param_vals[NUM_SIM_PARAMS] = {gdev, gmin, ckt->CKTnomTemp, + simulatorVersion}; + OsdiSimParas sim_params_ = {.names = sim_params, + .vals = sim_param_vals, + .names_str = sim_params_str, + .vals_str = NULL}; + + OsdiRegistryEntry entry = osdi_reg_entry_model(inModel); + const OsdiDescriptor *descr = entry.descriptor; + + for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) { + void *model = osdi_model_data(gen_model); + + for (gen_inst = gen_model->GENinstances; gen_inst; + gen_inst = gen_inst->GENnextInstance) { + void *inst = osdi_instance_data(entry, gen_inst); + + /* hpyothetically this could run in parallel we do not write any shared + data here*/ + handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname}; + /* TODO initial conditions? */ + uint32_t ret_flags = descr->eval(&handle, inst, model, flags, + ckt->CKTrhsOld, &sim_params_); + + /* call to $fatal in Verilog-A abort!*/ + if (ret_flags & EVAL_RET_FLAG_FATAL) { + return E_PANIC; + } + + /* init small signal analysis does not require loading values into + * matrix/rhs*/ + if (is_init_smsig) { + continue; + } + + /* handle calls to $finish, $limit, $stop + * TODO actually do something with extra_inst_data->finish and + * extra_inst_data->limt + * */ + OsdiExtraInstData *extra_inst_data = + osdi_extra_instance_data(entry, gen_inst); + if (ret_flags & EVAL_RET_FLAG_FINISH) { + extra_inst_data->finish = true; + } + if (ret_flags & EVAL_RET_FLAG_LIM) { + extra_inst_data->lim = true; + } + if (ret_flags & EVAL_RET_FLAG_STOP) { + ret = (E_PAUSE); + } + + if (is_tran) { + /* load dc matrix and capacitances (charge derivative multiplied with + * CKTag[0]) */ + descr->load_jacobian_tran(inst, ckt->CKTag[0]); + + /* load static rhs and dynamic linearized rhs (SUM Vb * dIa/dVb)*/ + descr->load_spice_rhs_tran(inst, ckt->CKTrhs, ckt->CKTrhsOld, + ckt->CKTag[0]); + + uint32_t *node_mapping = + (uint32_t *)(((char *)inst) + descr->node_mapping_offset); + + double *residual_react = + (double *)(((char *)inst) + descr->residual_react_offset); + + /* use numeric integration to obtain the remainer of the RHS*/ + int state = gen_inst->GENstate; + for (uint32_t i = 0; i < descr->num_nodes; i++) { + if (descr->nodes[i].is_reactive) { + /* store charges in state vector*/ + ckt->CKTstate0[state] = residual_react[i]; + if (is_init_tran) { + ckt->CKTstate1[state] = residual_react[i]; + } + + /* we only care about the numeric integration itself not ceq/geq + because those are already calculated by load_jacobian_tran and + load_spice_rhs_tran*/ + NIintegrate(ckt, &dump, &dump, 0, state); + + /* add the numeric derivative to the rhs */ + ckt->CKTrhs[node_mapping[i]] -= ckt->CKTstate0[state + 1]; + + if (is_init_tran) { + ckt->CKTstate1[state + 1] = ckt->CKTstate0[state + 1]; + } + + state += 2; + } + } + } else { + /* copy internal derivatives into global matrix */ + descr->load_jacobian_resist(inst); + /* calculate spice RHS from internal currents and store into global RHS + */ + descr->load_spice_rhs_dc(inst, ckt->CKTrhs, ckt->CKTrhsOld); + } + } + } + return ret; +} diff --git a/src/spicelib/devices/osdi/osdiparam.c b/src/spicelib/devices/osdi/osdiparam.c new file mode 100644 index 000000000..eebc76811 --- /dev/null +++ b/src/spicelib/devices/osdi/osdiparam.c @@ -0,0 +1,159 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#include "ngspice/iferrmsg.h" +#include "ngspice/ngspice.h" +#include "ngspice/typedefs.h" + +#include "osdidefs.h" + +#include +#include + +static int osdi_param_access(OsdiParamOpvar *param_info, bool write_value, + IFvalue *value, void *ptr) { + size_t len; + void *val_ptr; + switch (param_info->flags & PARA_TY_MASK) { + case PARA_TY_REAL: + len = sizeof(double); + if (param_info->len) { + len *= param_info->len; + val_ptr = &value->v.vec.rVec; + } else { + val_ptr = &value->rValue; + } + break; + case PARA_TY_INT: + len = sizeof(int); + if (param_info->len) { + len *= param_info->len; + val_ptr = &value->v.vec.iVec; + } else { + val_ptr = &value->iValue; + } + break; + case PARA_TY_STR: + len = sizeof(char *); + if (param_info->len) { + len *= param_info->len; + val_ptr = &value->v.vec.cVec; + } else { + val_ptr = &value->cValue; + } + break; + default: + return (E_PARMVAL); + } + if (write_value) { + memcpy(val_ptr, ptr, len); + } else { + memcpy(ptr, val_ptr, len); + } + + return OK; +} + +static int osdi_write_param(void *dst, IFvalue *value, int param, + const OsdiDescriptor *descr) { + if (dst == NULL) { + return (E_PANIC); + } + + OsdiParamOpvar *param_info = &descr->param_opvar[param]; + + if (param_info->len) { + if ((uint32_t)value->v.numValue != param_info->len) { + return (E_PARMVAL); + } + } + + return osdi_param_access(param_info, false, value, dst); +} + +extern int OSDIparam(int param, IFvalue *value, GENinstance *instPtr, + IFvalue *select) { + + NG_IGNORE(select); + OsdiRegistryEntry entry = osdi_reg_entry_inst(instPtr); + const OsdiDescriptor *descr = entry.descriptor; + + if (param > (int)descr->num_instance_params) { + + // special handleing for temperature parameters + OsdiExtraInstData *inst = osdi_extra_instance_data(entry, instPtr); + if (param == (int)entry.dt) { + inst->dt = value->rValue; + inst->dt_given = true; + return (OK); + } + if (param == (int)entry.temp) { + inst->temp = value->rValue; + inst->temp_given = true; + return (OK); + } + + return (E_BADPARM); + } + + void *inst = osdi_instance_data(entry, instPtr); + void *dst = descr->access(inst, NULL, (uint32_t)param, + ACCESS_FLAG_SET | ACCESS_FLAG_INSTANCE); + + return osdi_write_param(dst, value, param, descr); +} + +extern int OSDImParam(int param, IFvalue *value, GENmodel *modelPtr) { + NG_IGNORE(select); + OsdiRegistryEntry entry = osdi_reg_entry_model(modelPtr); + const OsdiDescriptor *descr = entry.descriptor; + + if (param > (int)descr->num_params || + param < (int)descr->num_instance_params) { + return (E_BADPARM); + } + + void *model = osdi_model_data(modelPtr); + void *dst = descr->access(model, model, (uint32_t)param, ACCESS_FLAG_SET); + + return osdi_write_param(dst, value, param, descr); +} + +static int osdi_read_param(void *src, IFvalue *value, int id, + const OsdiDescriptor *descr) { + if (src == NULL) { + return (E_PANIC); + } + + OsdiParamOpvar *param_info = &descr->param_opvar[id]; + + if (param_info->len) { + value->v.numValue = (int)param_info->len; + } + + return osdi_param_access(param_info, true, value, src); +} + +extern int OSDIask(CKTcircuit *ckt, GENinstance *instPtr, int id, + IFvalue *value, IFvalue *select) { + NG_IGNORE(select); + NG_IGNORE(ckt); + + OsdiRegistryEntry entry = osdi_reg_entry_inst(instPtr); + void *inst = osdi_instance_data(entry, instPtr); + void *model = osdi_model_data_from_inst(instPtr); + + const OsdiDescriptor *descr = entry.descriptor; + + if (id > (int)(descr->num_params + descr->num_instance_params)) { + return (E_BADPARM); + } + uint32_t flags = ACCESS_FLAG_READ; + if (id < (int)descr->num_instance_params) { + flags |= ACCESS_FLAG_INSTANCE; + } + + void *src = descr->access(inst, model, (uint32_t)id, flags); + return osdi_read_param(src, value, id, descr); +} diff --git a/src/spicelib/devices/osdi/osdiregistry.c b/src/spicelib/devices/osdi/osdiregistry.c new file mode 100644 index 000000000..4a8e59ffa --- /dev/null +++ b/src/spicelib/devices/osdi/osdiregistry.c @@ -0,0 +1,243 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#include "ngspice/memory.h" +#include "ngspice/stringutil.h" +#include "osdidefs.h" + +#include "ngspice/iferrmsg.h" +#include "osdi.h" +#include +#include +#include + +#if defined(_MSC_VER) +#include +#define getcwd() _getcwd +#elif defined(__GNUC__) +#include +#endif + +// TODO: platform portability + +#include "dirent.h" +#include + +char *OSDI_Path = NULL; +OsdiRegistryEntry *registry = NULL; +uint32_t registry_off = 0; + +/** + * Calculates the offset that the OSDI instance data has from the beginning of + * the instance data allocated by ngspice. This offset is non trivial because + * ngspice must store the terminal pointers before the remaining instance data. + * As a result the offset is not constant and a variable amount of padding must + * be inserted to ensure correct alginment. + */ +static size_t calc_osdi_instance_data_off(const OsdiDescriptor *descr) { + size_t res = sizeof(GENinstance) /* generic data */ + + descr->num_terminals * sizeof(int); + size_t padding = sizeof(max_align_t) - res % sizeof(max_align_t); + if (padding == sizeof(max_align_t)) { + padding = 0; + } + return res + padding; +} + +/** + * Loads an object file from the hard drive with the platform equivalent of + * dlopen. This function checks that the OSDI version of the object file is + * valid and then writes all data into the `registry`. + * If any errors occur an appropriate message is written to errMsg + * + * @param PATH path A path to the shared object file + * @param uint32_t* len The amount of entries already written into `registry` + * @param uint32_t* capacity The amount of space available in `registry` before + * reallocation is required + * @returns -1 on error, 0 otherwise + */ +static int load_object_file(char *path, uint32_t *len, uint32_t *capacity) { + void *handle; + char *error; + handle = dlopen(path, RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + errMsg = dlerror(); + return -1; + } + dlerror(); /* Clear any existing error */ + + const uint32_t *OSDI_VERSION_MAJOR = dlsym(handle, "OSDI_VERSION_MAJOR"); + if ((error = dlerror()) != NULL) { + errMsg = dlerror(); + return -1; + } + + const uint32_t *OSDI_VERSION_MINOR = dlsym(handle, "OSDI_VERSION_MINOR"); + if ((error = dlerror()) != NULL) { + errMsg = + tprintf("Failed to load OSDI object file \"%s\": %s", path, dlerror()); + return -1; + } + + if (*OSDI_VERSION_MAJOR != OSDI_VERSION_MAJOR_CURR || + *OSDI_VERSION_MINOR != OSDI_VERSION_MINOR_CURR) { + errMsg = + tprintf("NGSPICE only supports OSDI v%d.%d but \"%s\" targets v%d.%d!", + OSDI_VERSION_MAJOR_CURR, OSDI_VERSION_MINOR_CURR, path, + *OSDI_VERSION_MAJOR, *OSDI_VERSION_MINOR); + return -1; + } + + const uint32_t *OSDI_NUM_DESCRIPTORS = dlsym(handle, "OSDI_NUM_DESCRIPTORS"); + if ((error = dlerror()) != NULL) { + errMsg = dlerror(); + return -1; + } + + const OsdiDescriptor *OSDI_DESCRIPTORS = dlsym(handle, "OSDI_DESCRIPTORS"); + if ((error = dlerror()) != NULL) { + errMsg = dlerror(); + return -1; + } + + if ((*len + *OSDI_NUM_DESCRIPTORS) > *capacity) { + *capacity *= 2; + registry = realloc(registry, *capacity); + } + + for (uint32_t i = 0; i < *OSDI_NUM_DESCRIPTORS; i++) { + const OsdiDescriptor *descr = &OSDI_DESCRIPTORS[i]; + + uint32_t dt = descr->num_params + descr->num_opvars; + uint32_t temp = descr->num_params + descr->num_opvars + 1; + for (uint32_t param_id = 0; param_id < descr->num_params; param_id++) { + OsdiParamOpvar *param = &descr->param_opvar[param_id]; + for (uint32_t j = 0; j < 1 + param->num_alias; j++) { + char *name = param->name[j]; + if (!strcmp(name, "dt")) { + dt = UINT32_MAX; + } else if (!strcasecmp(name, "dtemp") || !strcasecmp(name, "dt")) { + dt = param_id; + } else if (!strcmp(name, "temp")) { + temp = UINT32_MAX; + } else if (!strcasecmp(name, "temp") || + !strcasecmp(name, "temperature")) { + temp = param_id; + } + } + } + + size_t inst_off = calc_osdi_instance_data_off(descr); + registry[i + *len] = (OsdiRegistryEntry){ + .descriptor = descr, + .inst_offset = (uint32_t)inst_off, + .dt = dt, + .temp = temp, + }; + } + + *len += *OSDI_NUM_DESCRIPTORS; + + return 0; +} + +static char **osdi_search_dir(char *dir, char **dst, uint32_t *len, + uint32_t *cap) { + DIR *wdir = opendir(dir); + if (wdir == NULL) { + return dst; + } + struct direct *de; + while ((de = readdir(wdir)) != NULL) { + if (!(de->d_ino & (DT_REG | DT_LNK))) { + continue; + } + const char *filename = de->d_name; + const char *ext = strrchr(filename, '.'); + + if (!ext || ext == filename) { + continue; + } else { + ext += 1; + } + if (strcmp(ext, "so") != 0) { + continue; + } + if (*len >= *cap) { + *cap *= 2; + dst = realloc(dst, *cap * sizeof(char *)); + } + char *path = tprintf("%s%s%s", dir, DIR_PATHSEP, filename); + + if (de->d_ino & DT_LNK) { + char *full_path = realpath(path, NULL); + tfree(path); + path = full_path; + } + + dst[*len] = path; + *len += 1; + } + return dst; +} + +static char **osdi_search_devices(uint32_t *len) { + uint32_t cap = 8; + *len = 0; + char **dst = TMALLOC(char *, cap); + dst = osdi_search_dir(OSDI_Path, dst, len, &cap); + char *cwd = getcwd(NULL, 0); + if (cwd) { + char *local_osdi_dir = tprintf("%s%s%s", cwd, DIR_PATHSEP, "osdi"); + dst = osdi_search_dir(local_osdi_dir, dst, len, &cap); + tfree(cwd); + tfree(local_osdi_dir); + } + return dst; +} + +/** + * Loads a list of object files into the global OSDI registry. + * This function must only be called once at program startup. + * + * @param PATH *object_files The paths of the object files + * @param uint32_t num_files The number of object files to load + * @returns The number of loaded descriptors + */ +static int osdi_load_devices_from_files(char **object_files, + uint32_t num_files) { + uint32_t len = 0; + uint32_t capacity = num_files + 8; + registry = TMALLOC(OsdiRegistryEntry, capacity); + for (uint32_t i = 0; i < num_files; i++) { + int res = load_object_file(object_files[i], &len, &capacity); + if (res) { + return res; + } + } + registry = realloc(registry, len); + return (int)len; +} + +extern int osdi_load_devices(void) { + uint32_t len = 0; + char **files = osdi_search_devices(&len); + int res = osdi_load_devices_from_files(files, len); + // free paths + for (uint32_t i = 0; i < len; i++) { + tfree(files[i]); + } + tfree(files); + return res; +} + +size_t osdi_instance_data_off(OsdiRegistryEntry entry); +void *osdi_instance_data(OsdiRegistryEntry entry, GENinstance *inst); +OsdiExtraInstData *osdi_extra_instance_data(OsdiRegistryEntry entry, + GENinstance *inst); +size_t osdi_model_data_off(void); +void *osdi_model_data(GENmodel *model); +void *osdi_model_data_from_inst(GENinstance *inst); +OsdiRegistryEntry osdi_reg_entry_model(GENmodel *model); +OsdiRegistryEntry osdi_reg_entry_inst(GENinstance *inst); diff --git a/src/spicelib/devices/osdi/osdisetup.c b/src/spicelib/devices/osdi/osdisetup.c new file mode 100644 index 000000000..a86c9e474 --- /dev/null +++ b/src/spicelib/devices/osdi/osdisetup.c @@ -0,0 +1,365 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + */ + +#include "ngspice/iferrmsg.h" +#include "ngspice/memory.h" +#include "ngspice/ngspice.h" +#include "ngspice/typedefs.h" + +#include "osdidefs.h" + +#include +#include +#include + +/* + * Handles any errors raised by the setup_instance and setup_model functions + */ +static int handle_init_info(OsdiInitInfo info, const OsdiDescriptor *descr) { + if (info.flags & (EVAL_RET_FLAG_FATAL | EVAL_RET_FLAG_FINISH)) { + return (E_PANIC); + } + if (info.num_errors == 0) { + return (OK); + } + + for (uint32_t i = 0; i < info.num_errors; i++) { + OsdiInitError *err = &info.errors[i]; + switch (err->code) { + case INIT_ERR_OUT_OF_BOUNDS: { + char *param = descr->param_opvar[err->payload.parameter_id].name[0]; + printf("Parameter %s is out of bounds!", param); + break; + } + default: + printf("Unkown OSDO init error code %d!", err->code); + } + } + free(info.errors); + errMsg = "Errors occurred during initalization"; + return (E_PRIVATE); +} + +/* + * The OSDI instance data contains the `node_mapping` array. + * Here an index is stored for each node. This function initalizes this array + * with its indecies {0, 1, 2, 3, .., n}. + * The node collapsing information generated by setup_instance is used to + * replace these initial indecies with those that a node is collapsed into. + * For example collapsing nodes i and j sets node_mapping[i] = j. + * + * Terminals can never be collapsed in ngspice because they are allocated by + * ngspice instead of OSDI. Therefore any node collapsing that involves nodes + * `i < connected_terminals` is ignored. + * + * @param const OsdiDescriptor *descr The OSDI descriptor + * @param void *inst The instance data connected_terminals + * @param uint32_t connected_terminals The number of terminals that are not + * internal nodes. + * + * @returns The number of nodes required after collapsing. + * */ +static uint32_t collapse_nodes(const OsdiDescriptor *descr, void *inst, + uint32_t connected_terminals) { + /* access data inside instance */ + uint32_t *node_mapping = + (uint32_t *)(((char *)inst) + descr->node_mapping_offset); + bool *is_collapsible = + (bool *)(((char *)inst) + descr->is_collapsible_offset); + + /* without collapsing just return the total number of nodes */ + uint32_t num_nodes = descr->num_nodes; + + /* populate nodes with themselves*/ + for (uint32_t i = 0; i < descr->num_nodes; i++) { + node_mapping[i] = i; + } + + for (uint32_t i = 0; i < descr->num_collapsible; i++) { + /* check if the collapse hint (V(x,y) <+ 0) was executed */ + if (!is_collapsible[i]) { + continue; + } + + uint32_t from = descr->collapsible[i].node_1; + uint32_t to = descr->collapsible[i].node_2; + + /* terminal scan not be collapsed because these are created by the simulator + */ + if (node_mapping[from] < connected_terminals && + (to == descr->num_nodes || node_mapping[to] < connected_terminals || + node_mapping[to] == descr->num_nodes)) { + continue; + } + /* ensure that from is always the smaller node */ + if (to != descr->num_nodes && node_mapping[from] < node_mapping[to]) { + uint32_t temp = from; + from = to; + to = temp; + } + + from = node_mapping[from]; + if (node_mapping[to] != descr->num_nodes) { + to = node_mapping[to]; + } + + /* replace nodes mapped to from with to and reduce the number of nodes */ + for (uint32_t j = 0; j < descr->num_collapsible; j++) { + if (node_mapping[j] == from) { + node_mapping[j] = to; + } else if (node_mapping[j] > from) { + node_mapping[j] -= 1; + } + } + num_nodes -= 1; + } + return num_nodes; +} + +/* replace node mapping local to the current instance (created by + * collapse_nodes) with global node indicies allocated with CKTmkVolt */ +static void write_node_mapping(const OsdiDescriptor *descr, void *inst, + uint32_t *nodes) { + uint32_t *node_mapping = + (uint32_t *)(((char *)inst) + descr->node_mapping_offset); + for (uint32_t i = 0; i < descr->num_nodes; i++) { + if (node_mapping[i] == descr->num_nodes) { + /* gnd node */ + node_mapping[i] = 0; + } else { + node_mapping[i] = nodes[node_mapping[i]]; + } + } +} + +static int init_matrix(SMPmatrix *matrix, const OsdiDescriptor *descr, + void *inst) { + uint32_t *node_mapping = + (uint32_t *)(((char *)inst) + descr->node_mapping_offset); + + double **jacobian_ptr_resist = + (double **)(((char *)inst) + descr->jacobian_ptr_resist_offset); + + double **jacobian_ptr_react = + (double **)(((char *)inst) + descr->jacobian_ptr_react_offset); + + for (uint32_t i = 0; i < descr->num_jacobian_entries; i++) { + uint32_t equation = descr->jacobian_entries[i].node_1; + uint32_t unkown = descr->jacobian_entries[i].node_2; + equation = node_mapping[equation]; + unkown = node_mapping[unkown]; + double *ptr = SMPmakeElt(matrix, (int)equation, (int)unkown); + + if (ptr == NULL) { + return (E_NOMEM); + } + jacobian_ptr_resist[i] = ptr; + // complex number for ac analysis + jacobian_ptr_react[i] = ptr + 1; + } + return (OK); +} + +int OSDIsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt, + int *states) { + OsdiInitInfo init_info; + OsdiNgspiceHandle handle; + GENmodel *gen_model; + int res; + int error; + CKTnode *tmp; + GENinstance *gen_inst; + int err; + + OsdiRegistryEntry entry = osdi_reg_entry_model(inModel); + const OsdiDescriptor *descr = entry.descriptor; + + /* setup a temporary buffer */ + uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes); + + /* determine the number of states required by each instance */ + int num_states = 0; + for (uint32_t i = 0; i < descr->num_nodes; i++) { + if (descr->nodes[i].is_reactive) { + num_states += 2; + } + } + + for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) { + void *model = osdi_model_data(gen_model); + + /* setup model parameter (setup_model)*/ + handle = (OsdiNgspiceHandle){.kind = 1, .name = gen_model->GENmodName}; + init_info = descr->setup_model((void *)&handle, model); + res = handle_init_info(init_info, descr); + if (res) { + errRtn = "OSDI setup_model"; + continue; + } + + for (gen_inst = gen_model->GENinstances; gen_inst; + gen_inst = gen_inst->GENnextInstance) { + void *inst = osdi_instance_data(entry, gen_inst); + + /* special handling for temperature parameters */ + double temp = ckt->CKTtemp; + OsdiExtraInstData *extra_inst_data = + osdi_extra_instance_data(entry, gen_inst); + if (extra_inst_data->temp_given) { + temp = extra_inst_data->temp; + } + if (extra_inst_data->dt_given) { + temp += extra_inst_data->dt; + } + + /* find number of connected ports to allow evaluation of $port_connected + * and to handle node collapsing correctly later + * */ + int *terminals = (int *)(gen_inst + 1); + uint32_t connected_terminals = descr->num_terminals; + for (uint32_t i = 0; i < descr->num_terminals; i++) { + if (terminals[i] == -1) { + connected_terminals = i; + break; + } + } + + /* calculate op independent data, init instance parameters and determine + which collapsing occurs*/ + handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname}; + init_info = descr->setup_instance((void *)&handle, inst, model, temp, + connected_terminals); + res = handle_init_info(init_info, descr); + if (res) { + errRtn = "OSDI setup_instance"; + continue; + } + + /* setup the instance nodes */ + + uint32_t num_nodes = collapse_nodes(descr, inst, connected_terminals); + /* copy terminals */ + memcpy(node_ids, gen_inst + 1, sizeof(int) * connected_terminals); + /* create internal nodes as required */ + for (uint32_t i = connected_terminals; i < num_nodes; i++) { + // TODO handle currents correctly + error = CKTmkVolt(ckt, &tmp, gen_inst->GENname, descr->nodes[i].name); + if (error) + return (error); + node_ids[i] = (uint32_t)tmp->number; + // TODO nodeset? + } + write_node_mapping(descr, inst, node_ids); + + /* now that we have the node mapping we can create the matrix entries */ + err = init_matrix(matrix, descr, inst); + if (err) { + return err; + } + + /* reserve space in the state vector*/ + gen_inst->GENstate = *states; + *states += num_states; + } + } + + free(node_ids); + + return (OK); +} + +/* OSDI does not differentiate between setup and temperature update so we just + * call the setup routines again and assume that node collapsing (and therefore + * node mapping) stays the same + */ +extern int OSDItemp(GENmodel *inModel, CKTcircuit *ckt) { + OsdiInitInfo init_info; + OsdiNgspiceHandle handle; + GENmodel *gen_model; + int res; + GENinstance *gen_inst; + + OsdiRegistryEntry entry = osdi_reg_entry_model(inModel); + const OsdiDescriptor *descr = entry.descriptor; + + for (gen_model = inModel; gen_model != NULL; + gen_model = gen_model->GENnextModel) { + void *model = osdi_model_data(gen_model); + + handle = (OsdiNgspiceHandle){.kind = 1, .name = gen_model->GENmodName}; + init_info = descr->setup_model((void *)&handle, model); + res = handle_init_info(init_info, descr); + if (res) { + errRtn = "OSDI setup_model (OSDItemp)"; + continue; + } + + for (gen_inst = gen_model->GENinstances; gen_inst != NULL; + gen_inst = gen_inst->GENnextInstance) { + void *inst = osdi_instance_data(entry, gen_inst); + + // special handleing for temperature parameters + double temp = ckt->CKTtemp; + OsdiExtraInstData *extra_inst_data = + osdi_extra_instance_data(entry, gen_inst); + if (extra_inst_data->temp_given) { + temp = extra_inst_data->temp; + } + if (extra_inst_data->dt_given) { + temp = extra_inst_data->dt; + } + + handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname}; + // TODO optional terminals + init_info = descr->setup_instance((void *)&handle, inst, model, temp, + descr->num_terminals); + res = handle_init_info(init_info, descr); + if (res) { + errRtn = "OSDI setup_instance (OSDItemp)"; + continue; + } + // TODO check that there are no changes in node collapse? + } + } + return (OK); +} + +/* delete internal nodes + */ +extern int OSDIunsetup(GENmodel *inModel, CKTcircuit *ckt) { + GENmodel *gen_model; + GENinstance *gen_inst; + int num; + + OsdiRegistryEntry entry = osdi_reg_entry_model(inModel); + const OsdiDescriptor *descr = entry.descriptor; + + for (gen_model = inModel; gen_model != NULL; + gen_model = gen_model->GENnextModel) { + + for (gen_inst = gen_model->GENinstances; gen_inst != NULL; + gen_inst = gen_inst->GENnextInstance) { + void *inst = osdi_instance_data(entry, gen_inst); + + uint32_t *node_mapping = + (uint32_t *)(((char *)inst) + descr->node_mapping_offset); + for (uint32_t i = 0; i < descr->num_nodes; i++) { + num = (int)node_mapping[i]; + // hand coded implementations just know which nodes were collapsed + // however nodes may be collapsed multiple times so we can't easily use + // an approach like that instead we delete all nodes + // Deleting twiche with CKLdltNNum is fine (entry is already removed + // from the linked list and therefore no action is taken). + // However CKTdltNNum (rightfully) throws an error when trying to delete + // an external node. Therefore we need to check for each node that it is + // an internal node + if (ckt->prev_CKTlastNode->number && + num > ckt->prev_CKTlastNode->number) { + CKTdltNNum(ckt, num); + } + } + } + } + return (OK); +} diff --git a/src/spicelib/parser/Makefile.am b/src/spicelib/parser/Makefile.am index 5ef6e38ca..e2cece066 100644 --- a/src/spicelib/parser/Makefile.am +++ b/src/spicelib/parser/Makefile.am @@ -8,6 +8,7 @@ EXTRA_DIST = inpptree-parser-y.h libinp_la_SOURCES = \ ifeval.c \ ifnewuid.c \ + inp2a.c \ inp2b.c \ inp2c.c \ inp2d.c \ diff --git a/src/spicelib/parser/inp2a.c b/src/spicelib/parser/inp2a.c new file mode 100644 index 000000000..786fb718d --- /dev/null +++ b/src/spicelib/parser/inp2a.c @@ -0,0 +1,99 @@ +/********** +Copyright 1990 Regents of the University of California. All rights reserved. +Author: 1988 Thomas L. Quarles +Modified: 2001 Paolo Nenzi (Cider Integration) +**********/ + +#include "ngspice/devdefs.h" +#include "ngspice/fteext.h" +#include "ngspice/ifsim.h" +#include "ngspice/inpdefs.h" +#include "ngspice/inpmacs.h" +#include "ngspice/ngspice.h" + +#include "inpxx.h" +#include + +void INP2A(CKTcircuit *ckt, INPtables *tab, struct card *current) { + /* Mname [L=] + * [W=] [AD=] [AS=] [PD=] + * [PS=] [NRD=] [NRS=] [OFF] + * [IC=,,] + */ + + int type; /* the type the model says it is */ + char *line; /* the part of the current line left to parse */ + char *name; /* the resistor's name */ + // limit to at most 20 nodes + const int max_i = 20; + CKTnode *node[20]; + int error; /* error code temporary */ + int numnodes; /* flag indicating 4 or 5 (or 6 or 7) nodes */ + GENinstance *fast; /* pointer to the actual instance */ + int waslead; /* flag to indicate that funny unlabeled number was found */ + double leadval; /* actual value of unlabeled number */ + INPmodel *thismodel; /* pointer to model description for user's model */ + GENmodel *mdfast; /* pointer to the actual model */ + int i; + + line = current->line; + + INPgetNetTok(&line, &name, 1); + INPinsert(&name, tab); + + for (i = 0;; i++) { + char *token; + INPgetNetTok(&line, &token, 1); + + if (i >= 2) { + txfree(INPgetMod(ckt, token, &thismodel, tab)); + + /* check if using model binning -- pass in line since need 'l' and 'w' */ + if (!thismodel) + txfree(INPgetModBin(ckt, token, &thismodel, tab, line)); + + if (thismodel) { + INPinsert(&token, tab); + break; + } + } + if (i >= max_i) { + LITERR("could not find a valid modelname"); + return; + } + INPtermInsert(ckt, &token, tab, &node[i]); + } + + type = thismodel->INPmodType; + mdfast = thismodel->INPmodfast; + IFdevice *dev = ft_sim->devices[type]; + + if (!(dev->flags & DEV_OSDI)) { + LITERR("incorrect model type! Expected OSDI device"); + return; + } + + if (i == 0) { + LITERR("not enough nodes"); + return; + } + + if (i > *dev->terms) { + LITERR("too many nodes connected to instance"); + return; + } + + numnodes = i; + + IFC(newInstance, (ckt, mdfast, &fast, name)); + + for (i = 0; i < *dev->terms; i++) + if (i < numnodes) + IFC(bindNode, (ckt, fast, i + 1, node[i])); + else + GENnode(fast)[i] = -1; + + PARSECALL((&line, ckt, type, fast, &leadval, &waslead, tab)); + if (waslead) + LITERR(" error: no unlabeled parameter permitted on osdi devices\n"); +} diff --git a/src/spicelib/parser/inpdomod.c b/src/spicelib/parser/inpdomod.c index f4a30ce35..b63f911b9 100644 --- a/src/spicelib/parser/inpdomod.c +++ b/src/spicelib/parser/inpdomod.c @@ -3,10 +3,13 @@ Copyright 1990 Regents of the University of California. All rights reserved. Author: 1985 Thomas L. Quarles **********/ +#include "ngspice/fteext.h" #include "ngspice/ngspice.h" #include "ngspice/iferrmsg.h" #include "ngspice/inpdefs.h" +#include "ngspice/devdefs.h" #include "inpxx.h" +#include /*-------------------------------------------------------------- * This fcn takes the model card & examines it. Depending upon @@ -42,6 +45,22 @@ char *INPdomodel(CKTcircuit *ckt, struct card *image, INPtables * tab) INPinsert(&modname, tab); /* stick model name into table */ INPgetTok(&line, &type_name, 1); /* get model type */ + if (!strcmp(type_name, "osdi") || !strcmp(type_name, "veriloga") || + !strcmp(type_name, "va")) { + char *module; + INPgetTok(&line, &module, 1); /* get model type */ + if (module) { + type = INPtypelook_flag(module, DEV_OSDI); + if (type < 0) { + err = tprintf( + "%s module %s was not found in any of the loaded libraries\n", + type_name, module); + } + } else { + err = tprintf("Device type %s requires a module\n", type_name); + } + INPmakeMod(modname, type, image); + } else /* ----- Check if model is a BJT --------- */ if (strcmp(type_name, "npn") == 0 || strcmp(type_name, "pnp") == 0) { err = INPfindLev(line,&lev); diff --git a/src/spicelib/parser/inpfindl.c b/src/spicelib/parser/inpfindl.c index 72d515195..f69110d98 100644 --- a/src/spicelib/parser/inpfindl.c +++ b/src/spicelib/parser/inpfindl.c @@ -13,6 +13,7 @@ Modified: 1999 Paolo Nenzi - Now we can use a two digits level code - #include "ngspice/ngspice.h" #include "ngspice/inpdefs.h" #include "inpxx.h" +#include char *INPfindLev(char *line, int *level) { @@ -68,3 +69,41 @@ char *INPfindLev(char *line, int *level) return (NULL); } } + + + +char *INPfindModule(char *line) { + char *where; + + /* + *where = line; + */ + + where = strstr(line, "module"); + + if (where != NULL) { /* found a level keyword on the line */ + + where += 6; /* skip the module keyword */ + while ((*where == ' ') || (*where == '\t') || + (*where == '=')) { /* legal white space - ignore */ + where++; + } + char *end = where; + while (isalpha(*end) || isdigit(*end) || *end == '_' || + *end == '$') { /* find end of word */ + end++; + } + + + return dup_string(where, (size_t)end - (size_t)where); + } + + else { /* no module on the line => default */ +#ifdef TRACE + fprintf(stderr, + "Warning -- Module not specified on line \"%s\"\nUsing level 1.\n", + line); +#endif + return (NULL); + } +} diff --git a/src/spicelib/parser/inpgmod.c b/src/spicelib/parser/inpgmod.c index cd5aa8d9f..2ddc182b4 100644 --- a/src/spicelib/parser/inpgmod.c +++ b/src/spicelib/parser/inpgmod.c @@ -13,8 +13,11 @@ Modified: 2001 Paolo Nenzi (Cider Integration) #include "ngspice/cpstd.h" #include "ngspice/fteext.h" #include "ngspice/compatmode.h" +#include "ngspice/devdefs.h" #include "inpxx.h" #include +#include +#include #ifdef CIDER @@ -111,6 +114,14 @@ create_model(CKTcircuit *ckt, INPmodel *modtmp, INPtables *tab) INPgetNetTok(&line, &parm, 1); /* throw away 'modname' */ tfree(parm); + /* osdi models don't accept their device type as an argument */ + if (device->flags & DEV_OSDI){ + INPgetNetTok(&line, &parm, 1); /* throw away osdi */ + tfree(parm); + INPgetNetTok(&line, &parm, 1); /* throw away module name */ + tfree(parm); + } + while (*line) { INPgetTok(&line, &parm, 1); if (!*parm) { @@ -130,6 +141,7 @@ create_model(CKTcircuit *ckt, INPmodel *modtmp, INPtables *tab) /* just grab the number and throw away */ /* since we already have that info from pass1 */ INPgetValue(ckt, &line, IF_REAL, tab); + } else { p = find_instance_parameter(parm, device); diff --git a/src/spicelib/parser/inppas2.c b/src/spicelib/parser/inppas2.c index c53c85922..333104d2b 100644 --- a/src/spicelib/parser/inppas2.c +++ b/src/spicelib/parser/inppas2.c @@ -248,6 +248,12 @@ void INPpas2(CKTcircuit *ckt, struct card *data, INPtables * tab, TSKtask *task) INP2B(ckt, tab, current); break; + case 'A': + /* Bname [V=expr] [I=expr] */ + /* Arbitrary source. */ + INP2A(ckt, tab, current); + break; + case '.': /* . Many possibilities */ if (INP2dot(ckt,tab,current,task,gnode)) return; diff --git a/src/spicelib/parser/inptyplk.c b/src/spicelib/parser/inptyplk.c index 8763427f9..9bae9e344 100644 --- a/src/spicelib/parser/inptyplk.c +++ b/src/spicelib/parser/inptyplk.c @@ -12,9 +12,10 @@ Author: 1985 Thomas L. Quarles #include "ngspice/cpdefs.h" #include "ngspice/fteext.h" #include "ngspice/ifsim.h" +#include "ngspice/devdefs.h" #include "inpxx.h" -int INPtypelook(char *type) +int INPtypelook_flag(char *type, int flag) { int i; @@ -34,7 +35,7 @@ int INPtypelook(char *type) printf("In INPtypelook, checking model type = %s against existing model = %s, . . .\n", type, ft_sim->devices[i]->name); #endif - if (ft_sim->devices[i] && strcasecmp(type, ft_sim->devices[i]->name) == 0) { + if (ft_sim->devices[i] && strcasecmp(type, ft_sim->devices[i]->name) == 0 && ft_sim->devices[i]->flags & flag) { /* found the device - return it */ #ifdef TRACE @@ -53,3 +54,13 @@ int INPtypelook(char *type) return -1; } + +// backwards compatability: only look for static devices +int INPtypelook(char *type) +{ + return INPtypelook_flag(type, DEV_STATIC); + +} + + + diff --git a/src/spicelib/parser/inpxx.h b/src/spicelib/parser/inpxx.h index afbb9d162..6d42b5669 100644 --- a/src/spicelib/parser/inpxx.h +++ b/src/spicelib/parser/inpxx.h @@ -8,6 +8,7 @@ /* inp2xx.c */ +void INP2A(CKTcircuit *ckt, INPtables *tab, struct card *current); void INP2B(CKTcircuit *ckt, INPtables *tab, struct card *current); void INP2C(CKTcircuit *ckt, INPtables *tab, struct card *current); void INP2D(CKTcircuit *ckt, INPtables *tab, struct card *current); diff --git a/test_cases/capacitor/.empty.txt b/test_cases/capacitor/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/cccs/.empty.txt b/test_cases/cccs/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/ccvs/.empty.txt b/test_cases/ccvs/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/diode/netlist.sp b/test_cases/diode/netlist.sp new file mode 100644 index 000000000..a32bb568c --- /dev/null +++ b/test_cases/diode/netlist.sp @@ -0,0 +1,44 @@ +OSDI Diode Test +.options abstol=1e-15 + + +* one voltage source for sweeping, one for sensing: +VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M) +Vsense Dx D DC 0 +* Rt T 0 1e10 *not supported Pascal? + +* model definitions: +.model dmod_built_in d( bv=5.0000000000e+01 is=1e-13 n=1.05 thermal=1 tnom=27 rth0=100 rs=5 cj0=1e-15 vj=0.5 m=0.6 ) +.model dmod_osdi osdi diode_va rs=5 is=1e-13 n=1.05 Rth=100 cj0=1e-15 vj=0.5 m=0.6 + +*OSDI Diode: +*OSDI_ACTIVATE*A1 D 0 T dmod_osdi + +*Built-in Diode: +*BUILT_IN_ACTIVATE*D1 D 0 T dmod_built_in + + +.control +set filetype=ascii +set wr_vecnames +set wr_singlescale + +* a DC sweep from 0.3V to 1V +dc Vd 0.3 1.0 0.01 +wrdata dc_sim.ngspice v(d) i(vsense) v(t) + +* an AC sweep at Vd=0.5V +alter VD=0.5 +ac dec 10 .01 10 +wrdata ac_sim.ngspice v(d) i(vsense) + +* a transient analysis +tran 100ms 500000ms +wrdata tr_sim.ngspice v(d) i(vsense) + +* print number of iterations +rusage totiter + +.endc + +.end \ No newline at end of file diff --git a/test_cases/diode/test_diode.py b/test_cases/diode/test_diode.py new file mode 100644 index 000000000..abcbcb39f --- /dev/null +++ b/test_cases/diode/test_diode.py @@ -0,0 +1,205 @@ +""" test OSDI simulation of diode +""" +import types +from pathlib import Path +from DMT.core import DutType, Sweep, specifiers, SimCon, Plot, MCard +from DMT.core.circuit import ( + Circuit, + CircuitElement, + RESISTANCE, + DIODE, + SHORT, + VOLTAGE, +) + +from DMT.ngspice import DutNgspice + +import os +import numpy as np +import subprocess +import pandas as pd + +# This test runs a DC, AC and Transient Simulation of a simple diode. +# The diode is available in the "OSDI" Git project and needs to be compiled to a shared object +# and then bet put into /usr/local/share/ngspice/osdi: +# +# > make osdi_diode +# > cp diode_osdi.so /usr/local/share/ngspice/osdi/diode_osdi.so +# +# The integration test proves the functioning of the OSDI interface. The Ngspice diode is quite +# complicated and the results are therefore not exactly the same. +# Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation. + +# specify location of Ngspice executable to be tested +ngspice_path = "../../../debug/src/ngspice" + + +def test_ngspice(): + directory = os.path.dirname(__file__) + path_netlist = os.path.join(directory, "netlist.sp") + + # open netlist and activate Ngspice diode + with open(path_netlist) as netlist_handle: + netlist_raw = netlist_handle.read() + + netlist_osdi = netlist_raw.replace("*OSDI_ACTIVATE*", "") + netlist_built_in = netlist_raw.replace("*BUILT_IN_ACTIVATE*", "") + + # make directories for test cases + dir_osdi = os.path.join(directory, "test_osdi") + dir_built_in = os.path.join(directory, "test_built_in") + for directory_i in [dir_osdi, dir_built_in]: + os.makedirs(directory_i, exist_ok=True) + + # write netlists + with open(os.path.join(dir_osdi, "netlist.sp"), "w") as netlist_handle: + netlist_handle.write(netlist_osdi) + + with open(os.path.join(dir_built_in, "netlist.sp"), "w") as netlist_handle: + netlist_handle.write(netlist_built_in) + + # run simulations with Ngspice + for directory_i in [dir_osdi, dir_built_in]: + subprocess.run( + [ + ngspice_path, + "netlist.sp", + "-b", + ], + cwd=directory_i, + ) + + # read DC simulation results + dc_data_osdi = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\s+") + dc_data_built_in = pd.read_csv( + os.path.join(dir_built_in, "dc_sim.ngspice"), sep="\s+" + ) + + id_osdi = dc_data_osdi["i(vsense)"].to_numpy() + id_built_in = dc_data_built_in["i(vsense)"].to_numpy() + + # read AC simulation results + ac_data_osdi = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\s+") + ac_data_built_in = pd.read_csv( + os.path.join(dir_built_in, "ac_sim.ngspice"), sep="\s+" + ) + + # read TR simulation results + tr_data_osdi = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\s+") + tr_data_built_in = pd.read_csv( + os.path.join(dir_built_in, "tr_sim.ngspice"), sep="\s+" + ) + + # test simulation results + id_osdi = dc_data_osdi["i(vsense)"].to_numpy() + id_built_in = dc_data_built_in["i(vsense)"].to_numpy() + np.testing.assert_allclose(id_osdi[0:20], id_built_in[0:20], rtol=0.01) + + return ( + dc_data_osdi, + dc_data_built_in, + ac_data_osdi, + ac_data_built_in, + tr_data_osdi, + tr_data_built_in, + ) + + +if __name__ == "__main__": + ( + dc_data_osdi, + dc_data_built_in, + ac_data_osdi, + ac_data_built_in, + tr_data_osdi, + tr_data_built_in, + ) = test_ngspice() + + import matplotlib.pyplot as plt + + # DC Plot + pd_built_in = dc_data_built_in["v(d)"] * dc_data_built_in["i(vsense)"] + pd_osdi = dc_data_osdi["v(d)"] * dc_data_osdi["i(vsense)"] + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + ax1.semilogy( + dc_data_built_in["v(d)"], + dc_data_built_in["i(vsense)"] * 1e3, + label="built-in", + linestyle=" ", + marker="x", + ) + ax1.semilogy( + dc_data_osdi["v(d)"], + dc_data_osdi["i(vsense)"] * 1e3, + label="OSDI", + ) + ax2.plot( + dc_data_built_in["v(d)"], + dc_data_built_in["v(t)"], + label="built-in", + linestyle=" ", + marker="x", + ) + ax2.plot( + dc_data_osdi["v(d)"], + dc_data_osdi["v(t)"], + label="OSDI", + ) + ax1.set_ylabel(r"$I_{\mathrm{D}} (\mathrm{mA})$") + ax2.set_ylabel(r"$\Delta T_{\mathrm{j}}(\mathrm{K})$") + ax1.set_xlabel(r"$V_{\mathrm{D}}(\mathrm{V})$") + plt.legend() + + # AC Plot + omega = 2 * np.pi * ac_data_osdi["frequency"] + fig = plt.figure() + plt.semilogx( + ac_data_built_in["frequency"], + ac_data_built_in["i(vsense)"] * 1e3, + label="built-in", + linestyle=" ", + marker="x", + ) + plt.semilogx( + ac_data_osdi["frequency"], ac_data_osdi["i(vsense)"] * 1e3, label="OSDI" + ) + plt.xlabel(r"$f(H)$") + plt.ylabel(r"$\Re \left\{ \underline{Y}_{11} \right\} (\mathrm{mS})$") + plt.legend() + fig = plt.figure() + plt.semilogx( + ac_data_built_in["frequency"], + ac_data_built_in["i(vsense).1"] * 1e3 / omega, + label="built-in", + linestyle=" ", + marker="x", + ) + plt.semilogx( + ac_data_osdi["frequency"], + ac_data_osdi["i(vsense).1"] * 1e3 / omega, + label="OSDI", + ) + plt.xlabel(r"$f(\mathrm{H})$") + plt.ylabel(r"$\Im \left\{ \underline{Y}_{11} \right\}/(\omega) (\mathrm{mF})$") + plt.legend() + + # TR plot + fig = plt.figure() + plt.plot( + tr_data_built_in["time"] * 1e9, + tr_data_built_in["i(vsense)"] * 1e3, + label="built-in", + linestyle=" ", + marker="x", + ) + plt.plot( + tr_data_osdi["time"] * 1e9, + tr_data_osdi["i(vsense)"] * 1e3, + label="OSDI", + ) + plt.xlabel(r"$t(\mathrm{nS})$") + plt.ylabel(r"$I_{\mathrm{D}}(\mathrm{mA})$") + plt.legend() + + plt.show() diff --git a/test_cases/diode/test_osdi/netlist.sp b/test_cases/diode/test_osdi/netlist.sp new file mode 100644 index 000000000..2f89a65e3 --- /dev/null +++ b/test_cases/diode/test_osdi/netlist.sp @@ -0,0 +1,44 @@ +OSDI Diode Test +.options abstol=1e-15 + + +* one voltage source for sweeping, one for sensing: +VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M) +Vsense Dx D DC 0 +* Rt T 0 1e10 *not supported Pascal? + +* model definitions: +.model dmod_built_in d( bv=5.0000000000e+01 is=1e-13 n=1.05 thermal=1 tnom=27 rth0=100 rs=5 cj0=0 vj=0.5 m=0.6 ) +.model dmod_osdi osdi diode_va rs=5 is=1e-13 n=1.05 Rth=100 cj0=0 vj=0.5 m=0.6 + +*OSDI Diode: +A1 D 0 T dmod_osdi + +*Built-in Diode: +*BUILT_IN_ACTIVATE*D1 D 0 T dmod_built_in + + +.control +set filetype=ascii +set wr_vecnames +set wr_singlescale + +* a DC sweep from 0.3V to 1V +*dc Vd 0.3 1.0 0.01 +*wrdata dc_sim.ngspice v(d) i(vsense) v(t) + +* an AC sweep at Vd=0.5V +* alter VD=0.5 +* ac dec 10 .01 10 +* wrdata ac_sim.ngspice v(d) i(vsense) + +* a transient analysis +tran 100ms 500000ms +wrdata tr_sim.ngspice v(d) i(vsense) + +* print number of iterations +rusage totiter + +.endc + +.end \ No newline at end of file diff --git a/test_cases/hicuml2/.empty.txt b/test_cases/hicuml2/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/inductor/.empty.txt b/test_cases/inductor/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/multiple_devices/.empty.txt b/test_cases/multiple_devices/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/node_collapsing/.empty.txt b/test_cases/node_collapsing/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/resistor/.empty.txt b/test_cases/resistor/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/vccs/.empty.txt b/test_cases/vccs/.empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test_cases/vcvs/.empty.txt b/test_cases/vcvs/.empty.txt new file mode 100644 index 000000000..e69de29bb