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
This commit is contained in:
DSPOM 2022-04-20 18:12:10 +02:00
parent 5ada0cce8b
commit c44f22b116
41 changed files with 1962 additions and 5 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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 {

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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 \

View File

@ -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)

View File

@ -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

View File

@ -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 <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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);

View File

@ -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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
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);
}

View File

@ -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 <stddef.h>
#include <stdint.h>
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;

View File

@ -0,0 +1,29 @@
/*
* Copyright© 2022 SemiMod UG. All rights reserved.
*/
#pragma once
#include "ngspice/gendefs.h"
#include "ngspice/smpdefs.h"
#include <stdint.h>
#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 *); */

View File

@ -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 <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/**
* 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);
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright© 2022 SemiMod UG. All rights reserved.
*/
#pragma once
#include <stdint.h>
/**
* 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);

View File

@ -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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -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 <stdint.h>
#include <string.h>
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);
}

View File

@ -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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER)
#include <direct.h>
#define getcwd() _getcwd
#elif defined(__GNUC__)
#include <unistd.h>
#endif
// TODO: platform portability
#include "dirent.h"
#include <dlfcn.h>
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);

View File

@ -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 <stdint.h>
#include <stdio.h>
#include <string.h>
/*
* 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);
}

View File

@ -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 \

View File

@ -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 <stdio.h>
void INP2A(CKTcircuit *ckt, INPtables *tab, struct card *current) {
/* Mname <node> <node> <node> <node> <model> [L=<val>]
* [W=<val>] [AD=<val>] [AS=<val>] [PD=<val>]
* [PS=<val>] [NRD=<val>] [NRS=<val>] [OFF]
* [IC=<val>,<val>,<val>]
*/
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");
}

View File

@ -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 <stdio.h>
/*--------------------------------------------------------------
* 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);

View File

@ -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 <stdio.h>
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);
}
}

View File

@ -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 <errno.h>
#include <stdio.h>
#include <string.h>
#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);

View File

@ -248,6 +248,12 @@ void INPpas2(CKTcircuit *ckt, struct card *data, INPtables * tab, TSKtask *task)
INP2B(ckt, tab, current);
break;
case 'A':
/* Bname <node> <node> [V=expr] [I=expr] */
/* Arbitrary source. */
INP2A(ckt, tab, current);
break;
case '.': /* .<something> Many possibilities */
if (INP2dot(ckt,tab,current,task,gnode))
return;

View File

@ -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);
}

View File

@ -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);

View File

View File

View File

View File

@ -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

View File

@ -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()

View File

@ -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

View File

View File

View File

View File

View File

View File

View File