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