From 5932114781f71fce519f6863ec025f9b5e6ecd20 Mon Sep 17 00:00:00 2001 From: Markus Mueller Date: Tue, 10 May 2022 15:42:27 +0200 Subject: [PATCH] (testig) node collapsing bugfix --- src/osdi/osdisetup.c | 12 +- test_cases/capacitor/capacitor_va.c | 367 ---------- test_cases/diode/diode.c | 53 +- test_cases/node_collapsing/diode.c | 824 +++++++++++++++++++++++ test_cases/node_collapsing/netlist.sp | 46 ++ test_cases/node_collapsing/test_diode.py | 148 ++++ test_cases/resistor/netlist.sp | 2 +- test_cases/resistor/resistor.c | 2 +- 8 files changed, 1068 insertions(+), 386 deletions(-) delete mode 100644 test_cases/capacitor/capacitor_va.c create mode 100644 test_cases/node_collapsing/diode.c create mode 100644 test_cases/node_collapsing/netlist.sp create mode 100644 test_cases/node_collapsing/test_diode.py diff --git a/src/osdi/osdisetup.c b/src/osdi/osdisetup.c index 61340cee3..d796254db 100644 --- a/src/osdi/osdisetup.c +++ b/src/osdi/osdisetup.c @@ -87,14 +87,14 @@ static uint32_t collapse_nodes(const OsdiDescriptor *descr, void *inst, 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 + /* terminals created by the simulator cannot be collapsed */ 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 */ + /* ensure that to is always the smaller node */ if (to != descr->num_nodes && node_mapping[from] < node_mapping[to]) { uint32_t temp = from; from = to; @@ -102,16 +102,18 @@ static uint32_t collapse_nodes(const OsdiDescriptor *descr, void *inst, } from = node_mapping[from]; - if (node_mapping[to] != descr->num_nodes) { + if (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++) { + for (uint32_t j = 0; j < descr->num_nodes; j++) { if (node_mapping[j] == from) { node_mapping[j] = to; } else if (node_mapping[j] > from) { - node_mapping[j] -= 1; + if (node_mapping[j]!= num_nodes){ + node_mapping[j] -= 1; + } } } num_nodes -= 1; diff --git a/test_cases/capacitor/capacitor_va.c b/test_cases/capacitor/capacitor_va.c deleted file mode 100644 index 93599883d..000000000 --- a/test_cases/capacitor/capacitor_va.c +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright© 2022 SemiMod UG. All rights reserved. - * - * This is an exemplary implementation of the OSDI interface for the Verilog-A - * model specified in diode.va. In the future, the OpenVAF compiler shall - * generate an comparable object file. Primary purpose of this is example to - * have a concrete example for the OSDI interface, OpenVAF will generate a more - * optimized implementation. - * - */ - -#include "osdi.h" -#include "string.h" -#include -#include -#include -#include -#include - -// public interface -extern uint32_t OSDI_VERSION_MAJOR; -extern uint32_t OSDI_VERSION_MINOR; -extern uint32_t OSDI_NUM_DESCRIPTORS; -extern OsdiDescriptor OSDI_DESCRIPTORS[1]; - -// number of nodes and definitions of node ids for nicer syntax in this file -// note: order should be same as "nodes" list defined later -#define NUM_NODES 3 -#define P 0 -#define M 1 - -// number of matrix entries and definitions for Jacobian entries for nicer -// syntax in this file -#define NUM_MATRIX 4 -#define P_P 0 -#define P_M 1 -#define M_P 2 -#define M_M 3 - -// The model structure for the diode -typedef struct CapacitorModel -{ - double C; - bool C_given; -} CapacitorModel; - -// The instace structure for the diode -typedef struct CapacitorInstance -{ - double temperature; - double rhs_resist[NUM_NODES]; - double rhs_react[NUM_NODES]; - double jacobian_resist[NUM_MATRIX]; - double jacobian_react[NUM_MATRIX]; - double *jacobian_ptr_resist[NUM_MATRIX]; - double *jacobian_ptr_react[NUM_MATRIX]; - uint32_t node_off[NUM_NODES]; -} CapacitorInstance; - -// implementation of the access function as defined by the OSDI spec -void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags) -{ - CapacitorModel *model = (CapacitorModel *)model_; - CapacitorInstance *inst = (CapacitorInstance *)inst_; - - bool *given; - void *value; - - switch (id) // id of params defined in param_opvar array - { - case 0: - value = (void *)&model->C; - given = &model->C_given; - break; - default: - return NULL; - } - - if (flags & ACCESS_FLAG_SET) - { - *given = true; - } - - return value; -} - -// implementation of the setup_model function as defined in the OSDI spec -OsdiInitInfo setup_model(void *_handle, void *model_) -{ - CapacitorModel *model = (CapacitorModel *)model_; - - // set parameters and check bounds - if (!model->C_given) - { - model->C = 1e-15; - } - return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL}; -} - -// implementation of the setup_instace function as defined in the OSDI spec -OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_, - double temperature, uint32_t _num_terminals) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - CapacitorModel *model = (CapacitorModel *)model_; - - inst->temperature = temperature; - return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL}; -} - -// implementation of the eval function as defined in the OSDI spec -uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags, - double *prev_solve, OsdiSimParas *sim_params) -{ - CapacitorModel *model = (CapacitorModel *)model_; - CapacitorInstance *inst = (CapacitorInstance *)inst_; - - // get voltages - double vp = prev_solve[inst->node_off[P]]; - double vm = prev_solve[inst->node_off[M]]; - - double vpm = vp - vm; - - double gmin = 1e-12; - for (int i = 0; sim_params->names[i] != NULL; i++) - { - if (strcmp(sim_params->names[i], "gmin") == 0) - { - gmin = sim_params->vals[i]; - } - } - - double qc_vpm = model->C; - double qc = model->C * vpm; - - //////////////////////////////// - // evaluate model equations - //////////////////////////////// - - if (flags & CALC_REACT_RESIDUAL) - { - // write react rhs - inst->rhs_react[P] = qc; - inst->rhs_react[M] = -qc; - } - - ////////////////// - // write Jacobian - ////////////////// - - if (flags & CALC_REACT_JACOBIAN) - { - // write react matrix - // stamp Qd between nodes A and Ci depending also on dT - inst->jacobian_react[P_P] = qc_vpm; - inst->jacobian_react[P_M] = -qc_vpm; - inst->jacobian_react[M_P] = -qc_vpm; - inst->jacobian_react[M_M] = qc_vpm; - } - - return 0; -} - -// TODO implementation of the load_noise function as defined in the OSDI spec -void load_noise(void *inst, void *model, double freq, double *noise_dens, - double *ln_noise_dens) -{ - // TODO add noise to example -} - -#define LOAD_RHS_RESIST(name) \ - dst[inst->node_off[name]] += inst->rhs_resist[name]; - -// implementation of the load_rhs_resist function as defined in the OSDI spec -void load_residual_resist(void *inst_, double *dst) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - - LOAD_RHS_RESIST(P) - LOAD_RHS_RESIST(M) -} - -#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name]; - -// implementation of the load_rhs_react function as defined in the OSDI spec -void load_residual_react(void *inst_, double *dst) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - - LOAD_RHS_REACT(P) - LOAD_RHS_REACT(M) -} - -#define LOAD_MATRIX_RESIST(name) \ - *inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name]; - -// implementation of the load_matrix_resist function as defined in the OSDI spec -void load_jacobian_resist(void *inst_) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - LOAD_MATRIX_RESIST(P_P) - LOAD_MATRIX_RESIST(P_M) - LOAD_MATRIX_RESIST(M_P) - LOAD_MATRIX_RESIST(M_M) -} - -#define LOAD_MATRIX_REACT(name) \ - *inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha; - -// implementation of the load_matrix_react function as defined in the OSDI spec -void load_jacobian_react(void *inst_, double alpha) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - LOAD_MATRIX_REACT(P_P) - LOAD_MATRIX_REACT(M_M) - LOAD_MATRIX_REACT(P_M) - LOAD_MATRIX_REACT(M_P) -} - -#define LOAD_MATRIX_TRAN(name) \ - *inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha; - -// implementation of the load_matrix_tran function as defined in the OSDI spec -void load_jacobian_tran(void *inst_, double alpha) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - - // set dc stamps - load_jacobian_resist(inst_); - - // add reactive contributions - LOAD_MATRIX_TRAN(P_P) - LOAD_MATRIX_TRAN(M_M) - LOAD_MATRIX_TRAN(M_P) - LOAD_MATRIX_TRAN(M_M) -} - -// implementation of the load_spice_rhs_dc function as defined in the OSDI spec -void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve) -{ - CapacitorInstance *inst = (CapacitorInstance *)inst_; - double vp = prev_solve[inst->node_off[P]]; - double vm = prev_solve[inst->node_off[M]]; - - dst[inst->node_off[P]] += inst->jacobian_resist[P_M] * vm + - inst->jacobian_resist[P_P] * vp - - inst->rhs_resist[P]; - - dst[inst->node_off[M]] += inst->jacobian_resist[M_P] * vp + - inst->jacobian_resist[M_M] * vm - - inst->rhs_resist[M]; -} - -// implementation of the load_spice_rhs_tran function as defined in the OSDI -// spec -void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve, - double alpha) -{ - - CapacitorInstance *inst = (CapacitorInstance *)inst_; - double vp = prev_solve[inst->node_off[P]]; - double vm = prev_solve[inst->node_off[M]]; - - // set DC rhs - load_spice_rhs_dc(inst_, dst, prev_solve); - - // add contributions due to reactive elements - dst[inst->node_off[P]] += - alpha * (inst->jacobian_react[P_P] * vp + - inst->jacobian_react[P_M] * vm); - - dst[inst->node_off[M]] += alpha * (inst->jacobian_react[M_M] * vm + - inst->jacobian_react[M_P] * vp); -} - -// structure that provides information of all nodes of the model -OsdiNode nodes[NUM_NODES] = { - {.name = "P", .units = "V", .is_reactive = true}, - {.name = "M", .units = "V", .is_reactive = true}, -}; - -// boolean array that tells which Jacobian entries are constant. Nothing is -// constant with selfheating, though. -bool const_jacobian_entries[NUM_MATRIX] = {}; -// these node pairs specify which entries in the Jacobian must be accounted for -OsdiNodePair jacobian_entries[NUM_MATRIX] = { - {P, P}, - {P, M}, - {M, P}, - {M, M}, -}; - -#define NUM_PARAMS 1 -// the model parameters as defined in Verilog-A, bounds and default values are -// stored elsewhere as they may depend on model parameters etc. -OsdiParamOpvar params[NUM_PARAMS] = { - { - .name = (char *[]){"C"}, - .num_alias = 0, - .description = "Capacitance", - .units = "Farad", - .flags = PARA_TY_REAL | PARA_KIND_MODEL, - .len = 0, - }, -}; - -// fill exported data -uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR; -uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR; -uint32_t OSDI_NUM_DESCRIPTORS = 1; -// this is the main structure used by simulators, it gives access to all -// information in a model -OsdiDescriptor OSDI_DESCRIPTORS[1] = {{ - // metadata - .name = "capacitor_va", - - // nodes - .num_nodes = NUM_NODES, - .num_terminals = 2, - .nodes = (OsdiNode *)&nodes, - - // matrix entries - .num_jacobian_entries = NUM_MATRIX, - .jacobian_entries = (OsdiNodePair *)&jacobian_entries, - .const_jacobian_entries = (bool *)&const_jacobian_entries, - - // memory - .instance_size = sizeof(CapacitorInstance), - .model_size = sizeof(CapacitorModel), - .residual_resist_offset = offsetof(CapacitorInstance, rhs_resist), - .residual_react_offset = offsetof(CapacitorInstance, rhs_react), - .node_mapping_offset = offsetof(CapacitorInstance, node_off), - .jacobian_resist_offset = offsetof(CapacitorInstance, jacobian_resist), - .jacobian_react_offset = offsetof(CapacitorInstance, jacobian_react), - .jacobian_ptr_resist_offset = offsetof(CapacitorInstance, jacobian_ptr_resist), - .jacobian_ptr_react_offset = offsetof(CapacitorInstance, jacobian_ptr_react), - - // TODO add node collapsing to example - // node collapsing - .num_collapsible = 0, - .collapsible = NULL, - .is_collapsible_offset = 0, - - // noise - .noise_sources = NULL, - .num_noise_src = 0, - - // parameters and op vars - .num_params = NUM_PARAMS, - .num_instance_params = 0, - .num_opvars = 0, - .param_opvar = (OsdiParamOpvar *)¶ms, - - // setup - .access = &osdi_access, - .setup_model = &setup_model, - .setup_instance = &setup_instance, - .eval = &eval, - .load_noise = &load_noise, - .load_residual_resist = &load_residual_resist, - .load_residual_react = &load_residual_react, - .load_spice_rhs_dc = &load_spice_rhs_dc, - .load_spice_rhs_tran = &load_spice_rhs_tran, - .load_jacobian_resist = &load_jacobian_resist, - .load_jacobian_react = &load_jacobian_react, - .load_jacobian_tran = &load_jacobian_tran, -}}; diff --git a/test_cases/diode/diode.c b/test_cases/diode/diode.c index c8bf339b0..85110ef5d 100644 --- a/test_cases/diode/diode.c +++ b/test_cases/diode/diode.c @@ -31,6 +31,8 @@ extern OsdiDescriptor OSDI_DESCRIPTORS[1]; #define TNODE 2 #define CI 3 +#define NUM_COLLAPSIBLE 2 + // number of matrix entries and definitions for Jacobian entries for nicer // syntax in this file #define NUM_MATRIX 14 @@ -89,6 +91,7 @@ typedef struct DiodeInstace double rhs_react[NUM_NODES]; double jacobian_resist[NUM_MATRIX]; double jacobian_react[NUM_MATRIX]; + bool is_collapsible[NUM_COLLAPSIBLE]; double *jacobian_ptr_resist[NUM_MATRIX]; double *jacobian_ptr_react[NUM_MATRIX]; uint32_t node_off[NUM_NODES]; @@ -264,6 +267,14 @@ OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_, DiodeInstace *inst = (DiodeInstace *)inst_; DiodeModel *model = (DiodeModel *)model_; + // Here the logic for node collapsing ist implemented. The indices in this list must adhere to the "collapsible" List of node pairs. + if (model->Rs<1e-9){ // Rs between Ci C + inst->is_collapsible[0] = true; + } + if (model->Rth<1e-9){ // Rs between Ci C + inst->is_collapsible[1] = true; + } + if (!inst->mfactor_given) { if (model->mfactor_given) @@ -337,13 +348,23 @@ uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags, 1.0 * exp((vaci / (model->N * vt)) - 1.0) * is_dt; // resistor - double irs = vcic / rs_t; - double g = 1.0 / rs_t; - double grt = -irs / rs_t * rs_dt; + double irs = 0; + double g = 0; + double grt = 0; + if (!inst->is_collapsible[0]) { + irs = vcic / rs_t; + g = 1.0 / rs_t; + grt = -irs / rs_t * rs_dt; + } + // thermal resistance - double irth = vdtj / rth_t; - double gt = 1.0 / rth_t - irth / rth_t * rth_dt; + double irth = 0; + double gt = 0; + if (!inst->is_collapsible[1]) { + irth = vdtj / rth_t; + gt = 1.0 / rth_t - irth / rth_t * rth_dt; + } // charge double vf = model->Vj * (1.0 - powf(3.04, -1.0 / model->M)); @@ -370,10 +391,15 @@ uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags, double qd_vaci = qd_vd * vd_vaci; // thermal power source = current source - double ith = id * vaci + powf(vcic, 2.0) / rs_t; - double ith_vtj = gdt * vaci - powf(vcic, 2.0) / rs_t / rs_t * rs_dt; - double ith_vcic = 2.0 * vcic / rs_t; + double ith = id * vaci ; + double ith_vtj = gdt * vaci ; + double ith_vcic = 0; double ith_vaci = gd * vaci + id; + if (!inst->is_collapsible[0]) { + ith_vcic = 2.0 * vcic / rs_t; + ith += powf(vcic, 2.0) / rs_t; + ith_vtj -= - powf(vcic, 2.0) / rs_t / rs_t * rs_dt; + } id += gmin * vaci; gd += gmin; @@ -629,6 +655,10 @@ OsdiNodePair jacobian_entries[NUM_MATRIX] = { {TNODE, C}, {TNODE, CI}, }; +OsdiNodePair collapsible[NUM_COLLAPSIBLE] = { + {CI, C}, + {TNODE, NUM_NODES}, +}; #define NUM_PARAMS 12 // the model parameters as defined in Verilog-A, bounds and default values are @@ -763,11 +793,10 @@ OsdiDescriptor OSDI_DESCRIPTORS[1] = {{ .jacobian_ptr_resist_offset = offsetof(DiodeInstace, jacobian_ptr_resist), .jacobian_ptr_react_offset = offsetof(DiodeInstace, jacobian_ptr_react), - // TODO add node collapsing to example // node collapsing - .num_collapsible = 0, - .collapsible = NULL, - .is_collapsible_offset = 0, + .num_collapsible = NUM_COLLAPSIBLE, + .collapsible = collapsible, + .is_collapsible_offset = offsetof(DiodeInstace, is_collapsible), // noise .noise_sources = NULL, diff --git a/test_cases/node_collapsing/diode.c b/test_cases/node_collapsing/diode.c new file mode 100644 index 000000000..85110ef5d --- /dev/null +++ b/test_cases/node_collapsing/diode.c @@ -0,0 +1,824 @@ +/* + * Copyright© 2022 SemiMod UG. All rights reserved. + * + * This is an exemplary implementation of the OSDI interface for the Verilog-A + * model specified in diode.va. In the future, the OpenVAF compiler shall + * generate an comparable object file. Primary purpose of this is example to + * have a concrete example for the OSDI interface, OpenVAF will generate a more + * optimized implementation. + * + */ + +#include "osdi.h" +#include "string.h" +#include +#include +#include +#include +#include + +// public interface +extern uint32_t OSDI_VERSION_MAJOR; +extern uint32_t OSDI_VERSION_MINOR; +extern uint32_t OSDI_NUM_DESCRIPTORS; +extern OsdiDescriptor OSDI_DESCRIPTORS[1]; + +// number of nodes and definitions of node ids for nicer syntax in this file +// note: order should be same as "nodes" list defined later +#define NUM_NODES 4 +#define A 0 +#define C 1 +#define TNODE 2 +#define CI 3 + +#define NUM_COLLAPSIBLE 2 + +// number of matrix entries and definitions for Jacobian entries for nicer +// syntax in this file +#define NUM_MATRIX 14 +#define CI_CI 0 +#define CI_C 1 +#define C_CI 2 +#define C_C 3 +#define A_A 4 +#define A_CI 5 +#define CI_A 6 +#define A_TNODE 7 +#define C_TNODE 8 +#define CI_TNODE 9 +#define TNODE_TNODE 10 +#define TNODE_A 11 +#define TNODE_C 12 +#define TNODE_CI 13 + +// The model structure for the diode +typedef struct DiodeModel +{ + double Rs; + bool Rs_given; + double Is; + bool Is_given; + double zetars; + bool zetars_given; + double N; + bool N_given; + double Cj0; + bool Cj0_given; + double Vj; + bool Vj_given; + double M; + bool M_given; + double Rth; + bool Rth_given; + double zetarth; + bool zetarth_given; + double zetais; + bool zetais_given; + double Tnom; + bool Tnom_given; + double mfactor; // multiplication factor for parallel devices + bool mfactor_given; + // InitError errors[MAX_ERROR_NUM], +} DiodeModel; + +// The instace structure for the diode +typedef struct DiodeInstace +{ + double mfactor; // multiplication factor for parallel devices + bool mfactor_given; + double temperature; + double rhs_resist[NUM_NODES]; + double rhs_react[NUM_NODES]; + double jacobian_resist[NUM_MATRIX]; + double jacobian_react[NUM_MATRIX]; + bool is_collapsible[NUM_COLLAPSIBLE]; + double *jacobian_ptr_resist[NUM_MATRIX]; + double *jacobian_ptr_react[NUM_MATRIX]; + uint32_t node_off[NUM_NODES]; +} DiodeInstace; + +#define EXP_LIM 80.0 + +double limexp(double x) +{ + if (x < EXP_LIM) + { + return exp(x); + } + else + { + return exp(EXP_LIM) * (x + 1 - EXP_LIM); + } +} + +double dlimexp(double x) +{ + if (x < EXP_LIM) + { + return exp(x); + } + else + { + return exp(EXP_LIM); + } +} + +// implementation of the access function as defined by the OSDI spec +void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags) +{ + DiodeModel *model = (DiodeModel *)model_; + DiodeInstace *inst = (DiodeInstace *)inst_; + + bool *given; + void *value; + + switch (id) // id of params defined in param_opvar array + { + case 0: + if (flags & ACCESS_FLAG_INSTANCE) + { + value = (void *)&inst->mfactor; + given = &inst->mfactor_given; + } + else + { + value = (void *)&model->mfactor; + given = &model->mfactor_given; + } + break; + case 1: + value = (void *)&model->Rs; + given = &model->Rs_given; + break; + case 2: + value = (void *)&model->Is; + given = &model->Is_given; + break; + case 3: + value = (void *)&model->zetars; + given = &model->zetars_given; + break; + case 4: + value = (void *)&model->N; + given = &model->N_given; + break; + case 5: + value = (void *)&model->Cj0; + given = &model->Cj0_given; + break; + case 6: + value = (void *)&model->Vj; + given = &model->Vj_given; + break; + case 7: + value = (void *)&model->M; + given = &model->M_given; + break; + case 8: + value = &model->Rth; + given = &model->Rth_given; + break; + case 9: + value = (void *)&model->zetarth; + given = &model->zetarth_given; + break; + case 10: + value = (void *)&model->zetais; + given = &model->zetais_given; + break; + case 11: + value = (void *)&model->Tnom; + given = &model->Tnom_given; + break; + default: + return NULL; + } + + if (flags & ACCESS_FLAG_SET) + { + *given = true; + } + + return value; +} + +// implementation of the setup_model function as defined in the OSDI spec +OsdiInitInfo setup_model(void *_handle, void *model_) +{ + DiodeModel *model = (DiodeModel *)model_; + + // set parameters and check bounds + if (!model->mfactor_given) + { + model->mfactor = 1.0; + } + if (!model->Rs_given) + { + model->Rs = 1e-9; + } + if (!model->Is_given) + { + model->Is = 1e-14; + } + if (!model->zetars_given) + { + model->zetars = 0; + } + if (!model->N_given) + { + model->N = 1; + } + if (!model->Cj0_given) + { + model->Cj0 = 0; + } + if (!model->Vj_given) + { + model->Vj = 1.0; + } + if (!model->M_given) + { + model->M = 0.5; + } + if (!model->Rth_given) + { + model->Rth = 0; + } + if (!model->zetarth_given) + { + model->zetarth = 0; + } + if (!model->zetais_given) + { + model->zetais = 0; + } + if (!model->Tnom_given) + { + model->Tnom = 300; + } + + return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL}; +} + +// implementation of the setup_instace function as defined in the OSDI spec +OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_, + double temperature, uint32_t _num_terminals) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + DiodeModel *model = (DiodeModel *)model_; + + // Here the logic for node collapsing ist implemented. The indices in this list must adhere to the "collapsible" List of node pairs. + if (model->Rs<1e-9){ // Rs between Ci C + inst->is_collapsible[0] = true; + } + if (model->Rth<1e-9){ // Rs between Ci C + inst->is_collapsible[1] = true; + } + + if (!inst->mfactor_given) + { + if (model->mfactor_given) + { + inst->mfactor = model->mfactor; + } + else + { + inst->mfactor = 1; + } + } + + inst->temperature = temperature; + return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL}; +} + +// implementation of the eval function as defined in the OSDI spec +uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags, + double *prev_solve, OsdiSimParas *sim_params) +{ + DiodeModel *model = (DiodeModel *)model_; + DiodeInstace *inst = (DiodeInstace *)inst_; + + // get voltages + double va = prev_solve[inst->node_off[A]]; + double vc = prev_solve[inst->node_off[C]]; + double vci = prev_solve[inst->node_off[CI]]; + double vdtj = prev_solve[inst->node_off[TNODE]]; + + double vcic = vci - vc; + double vaci = va - vci; + + double gmin = 1e-12; + for (int i = 0; sim_params->names[i] != NULL; i++) + { + if (strcmp(sim_params->names[i], "gmin") == 0) + { + gmin = sim_params->vals[i]; + } + } + + //////////////////////////////// + // evaluate model equations + //////////////////////////////// + + // temperature update + double pk = 1.3806503e-23; + double pq = 1.602176462e-19; + double t_dev = inst->temperature + vdtj; + double tdev_tnom = t_dev / model->Tnom; + double rs_t = model->Rs * powf(tdev_tnom, model->zetars); + double rth_t = model->Rth * powf(tdev_tnom, model->zetarth); + double is_t = model->Is * powf(tdev_tnom, model->zetais); + double vt = t_dev * pk / pq; + + // derivatives w.r.t. temperature + double rs_dt = model->zetars * model->Rs * + powf(tdev_tnom, model->zetars - 1.0) / model->Tnom; + double rth_dt = model->zetarth * model->Rth * + powf(tdev_tnom, model->zetarth - 1.0) / model->Tnom; + double is_dt = model->zetais * model->Is * + powf(tdev_tnom, model->zetais - 1.0) / model->Tnom; + double vt_tj = pk / pq; + + // evaluate model equations and calculate all derivatives + // diode current + double id = is_t * (limexp(vaci / (model->N * vt)) - 1.0); + double gd = is_t / vt * dlimexp(vaci / (model->N * vt)); + double gdt = -is_t * dlimexp(vaci / (model->N * vt)) * vaci / model->N / vt / + vt * vt_tj + + 1.0 * exp((vaci / (model->N * vt)) - 1.0) * is_dt; + + // resistor + double irs = 0; + double g = 0; + double grt = 0; + if (!inst->is_collapsible[0]) { + irs = vcic / rs_t; + g = 1.0 / rs_t; + grt = -irs / rs_t * rs_dt; + } + + + // thermal resistance + double irth = 0; + double gt = 0; + if (!inst->is_collapsible[1]) { + irth = vdtj / rth_t; + gt = 1.0 / rth_t - irth / rth_t * rth_dt; + } + + // charge + double vf = model->Vj * (1.0 - powf(3.04, -1.0 / model->M)); + double x = (vf - vaci) / vt; + double x_vt = -x / vt; + double x_dtj = x_vt * vt_tj; + double x_vaci = -1.0 / vt; + double y = sqrt(x * x + 1.92); + double y_x = 0.5 / y * 2.0 * x; + double y_vaci = y_x * x_vaci; + double y_dtj = y_x * x_dtj; + double vd = vf - vt * (x + y) / (2.0); + double vd_x = -vt / 2.0; + double vd_y = -vt / 2.0; + double vd_vt = -(x + y) / (2.0); + double vd_dtj = vd_x * x_dtj + vd_y * y_dtj + vd_vt * vt_tj; + double vd_vaci = vd_x * x_vaci + vd_y * y_vaci; + double qd = model->Cj0 * vaci * model->Vj * + (1.0 - powf(1.0 - vd / model->Vj, 1.0 - model->M)) / + (1.0 - model->M); + double qd_vd = model->Cj0 * model->Vj / (1.0 - model->M) * (1.0 - model->M) * + powf(1.0 - vd / model->Vj, 1.0 - model->M - 1.0) / model->Vj; + double qd_dtj = qd_vd * vd_dtj; + double qd_vaci = qd_vd * vd_vaci; + + // thermal power source = current source + double ith = id * vaci ; + double ith_vtj = gdt * vaci ; + double ith_vcic = 0; + double ith_vaci = gd * vaci + id; + if (!inst->is_collapsible[0]) { + ith_vcic = 2.0 * vcic / rs_t; + ith += powf(vcic, 2.0) / rs_t; + ith_vtj -= - powf(vcic, 2.0) / rs_t / rs_t * rs_dt; + } + + id += gmin * vaci; + gd += gmin; + + double mfactor = inst->mfactor; + + //////////////// + // write rhs + //////////////// + + if (flags & CALC_RESIST_RESIDUAL) + { + // write resist rhs + inst->rhs_resist[A] = id * mfactor; + inst->rhs_resist[CI] = -id * mfactor + irs * mfactor; + inst->rhs_resist[C] = -irs * mfactor; + inst->rhs_resist[TNODE] = -ith * mfactor + irth * mfactor; + } + + if (flags & CALC_REACT_RESIDUAL) + { + // write react rhs + inst->rhs_react[A] = qd * mfactor; + inst->rhs_react[CI] = -qd * mfactor; + } + + ////////////////// + // write Jacobian + ////////////////// + + if (flags & CALC_RESIST_JACOBIAN) + { + // stamp diode (current flowing from Ci into A) + inst->jacobian_resist[A_A] = gd * mfactor; + inst->jacobian_resist[A_CI] = -gd * mfactor; + inst->jacobian_resist[CI_A] = -gd * mfactor; + inst->jacobian_resist[CI_CI] = gd * mfactor; + // diode thermal + inst->jacobian_resist[A_TNODE] = gdt * mfactor; + inst->jacobian_resist[CI_TNODE] = -gdt * mfactor; + + // stamp resistor (current flowing from C into CI) + inst->jacobian_resist[CI_CI] += g * mfactor; + inst->jacobian_resist[CI_C] = -g * mfactor; + inst->jacobian_resist[C_CI] = -g * mfactor; + inst->jacobian_resist[C_C] = g * mfactor; + // resistor thermal + inst->jacobian_resist[CI_TNODE] = grt * mfactor; + inst->jacobian_resist[C_TNODE] = -grt * mfactor; + + // stamp rth flowing into node dTj + inst->jacobian_resist[TNODE_TNODE] = gt * mfactor; + + // stamp ith flowing out of T node + inst->jacobian_resist[TNODE_TNODE] -= ith_vtj * mfactor; + inst->jacobian_resist[TNODE_CI] = (ith_vcic - ith_vaci) * mfactor; + inst->jacobian_resist[TNODE_C] = -ith_vcic * mfactor; + inst->jacobian_resist[TNODE_A] = ith_vaci * mfactor; + } + + if (flags & CALC_REACT_JACOBIAN) + { + // write react matrix + // stamp Qd between nodes A and Ci depending also on dT + inst->jacobian_react[A_A] = qd_vaci * mfactor; + inst->jacobian_react[A_CI] = -qd_vaci * mfactor; + inst->jacobian_react[CI_A] = -qd_vaci * mfactor; + inst->jacobian_react[CI_CI] = qd_vaci * mfactor; + + inst->jacobian_react[A_TNODE] = qd_dtj * mfactor; + inst->jacobian_react[CI_TNODE] = -qd_dtj * mfactor; + } + + return 0; +} + +// TODO implementation of the load_noise function as defined in the OSDI spec +void load_noise(void *inst, void *model, double freq, double *noise_dens, + double *ln_noise_dens) +{ + // TODO add noise to example +} + +#define LOAD_RHS_RESIST(name) \ + dst[inst->node_off[name]] += inst->rhs_resist[name]; + +// implementation of the load_rhs_resist function as defined in the OSDI spec +void load_residual_resist(void *inst_, double *dst) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + + LOAD_RHS_RESIST(A) + LOAD_RHS_RESIST(CI) + LOAD_RHS_RESIST(C) + LOAD_RHS_RESIST(TNODE) +} + +#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name]; + +// implementation of the load_rhs_react function as defined in the OSDI spec +void load_residual_react(void *inst_, double *dst) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + + LOAD_RHS_REACT(A) + LOAD_RHS_REACT(CI) +} + +#define LOAD_MATRIX_RESIST(name) \ + *inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name]; + +// implementation of the load_matrix_resist function as defined in the OSDI spec +void load_jacobian_resist(void *inst_) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + LOAD_MATRIX_RESIST(A_A) + LOAD_MATRIX_RESIST(A_CI) + LOAD_MATRIX_RESIST(A_TNODE) + + LOAD_MATRIX_RESIST(CI_A) + LOAD_MATRIX_RESIST(CI_CI) + LOAD_MATRIX_RESIST(CI_C) + LOAD_MATRIX_RESIST(CI_TNODE) + + LOAD_MATRIX_RESIST(C_CI) + LOAD_MATRIX_RESIST(C_C) + LOAD_MATRIX_RESIST(C_TNODE) + + LOAD_MATRIX_RESIST(TNODE_TNODE) + LOAD_MATRIX_RESIST(TNODE_A) + LOAD_MATRIX_RESIST(TNODE_C) + LOAD_MATRIX_RESIST(TNODE_CI) +} + +#define LOAD_MATRIX_REACT(name) \ + *inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha; + +// implementation of the load_matrix_react function as defined in the OSDI spec +void load_jacobian_react(void *inst_, double alpha) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + LOAD_MATRIX_REACT(A_A) + LOAD_MATRIX_REACT(A_CI) + LOAD_MATRIX_REACT(CI_A) + LOAD_MATRIX_REACT(CI_CI) + + LOAD_MATRIX_REACT(A_TNODE) + LOAD_MATRIX_REACT(CI_TNODE) +} + +#define LOAD_MATRIX_TRAN(name) \ + *inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha; + +// implementation of the load_matrix_tran function as defined in the OSDI spec +void load_jacobian_tran(void *inst_, double alpha) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + + // set dc stamps + load_jacobian_resist(inst_); + + // add reactive contributions + LOAD_MATRIX_TRAN(A_A) + LOAD_MATRIX_TRAN(A_CI) + LOAD_MATRIX_TRAN(CI_A) + LOAD_MATRIX_TRAN(CI_CI) + + LOAD_MATRIX_TRAN(A_TNODE) + LOAD_MATRIX_TRAN(CI_TNODE) +} + +// implementation of the load_spice_rhs_dc function as defined in the OSDI spec +void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve) +{ + DiodeInstace *inst = (DiodeInstace *)inst_; + double va = prev_solve[inst->node_off[A]]; + double vci = prev_solve[inst->node_off[CI]]; + double vc = prev_solve[inst->node_off[C]]; + double vdtj = prev_solve[inst->node_off[TNODE]]; + + dst[inst->node_off[A]] += inst->jacobian_resist[A_A] * va + + inst->jacobian_resist[A_TNODE] * vdtj + + inst->jacobian_resist[A_CI] * vci - + inst->rhs_resist[A]; + + dst[inst->node_off[CI]] += inst->jacobian_resist[CI_A] * va + + inst->jacobian_resist[CI_TNODE] * vdtj + + inst->jacobian_resist[CI_CI] * vci - + inst->rhs_resist[CI]; + + dst[inst->node_off[C]] += inst->jacobian_resist[C_C] * vc + + inst->jacobian_resist[C_CI] * vci + + inst->jacobian_resist[C_TNODE] * vdtj - + inst->rhs_resist[C]; + + dst[inst->node_off[TNODE]] += inst->jacobian_resist[TNODE_A] * va + + inst->jacobian_resist[TNODE_C] * vc + + inst->jacobian_resist[TNODE_CI] * vci + + inst->jacobian_resist[TNODE_TNODE] * vdtj - + inst->rhs_resist[TNODE]; +} + +// implementation of the load_spice_rhs_tran function as defined in the OSDI +// spec +void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve, + double alpha) +{ + + DiodeInstace *inst = (DiodeInstace *)inst_; + double va = prev_solve[inst->node_off[A]]; + double vci = prev_solve[inst->node_off[CI]]; + double vdtj = prev_solve[inst->node_off[TNODE]]; + + // set DC rhs + load_spice_rhs_dc(inst_, dst, prev_solve); + + // add contributions due to reactive elements + dst[inst->node_off[A]] += + alpha * (inst->jacobian_react[A_A] * va + + inst->jacobian_react[A_CI] * vci + + inst->jacobian_react[A_TNODE] * vdtj); + + dst[inst->node_off[CI]] += alpha * (inst->jacobian_react[CI_CI] * vci + + inst->jacobian_react[CI_A] * va + + inst->jacobian_react[CI_TNODE] * vdtj); +} + +// structure that provides information of all nodes of the model +OsdiNode nodes[NUM_NODES] = { + {.name = "A", .units = "V", .is_reactive = true}, + {.name = "C", .units = "V"}, + {.name = "dT", .units = "K"}, + {.name = "CI", .units = "V", .is_reactive = true}, +}; + +// boolean array that tells which Jacobian entries are constant. Nothing is +// constant with selfheating, though. +bool const_jacobian_entries[NUM_MATRIX] = {}; +// these node pairs specify which entries in the Jacobian must be accounted for +OsdiNodePair jacobian_entries[NUM_MATRIX] = { + {CI, CI}, + {CI, C}, + {C, CI}, + {C, C}, + {A, A}, + {A, CI}, + {CI, A}, + {A, TNODE}, + {C, TNODE}, + {CI, TNODE}, + {TNODE, TNODE}, + {TNODE, A}, + {TNODE, C}, + {TNODE, CI}, +}; +OsdiNodePair collapsible[NUM_COLLAPSIBLE] = { + {CI, C}, + {TNODE, NUM_NODES}, +}; + +#define NUM_PARAMS 12 +// the model parameters as defined in Verilog-A, bounds and default values are +// stored elsewhere as they may depend on model parameters etc. +OsdiParamOpvar params[NUM_PARAMS] = { + { + .name = (char *[]){"$mfactor"}, + .num_alias = 0, + .description = "Verilog-A multiplication factor for parallel devices", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_INST, + .len = 0, + }, + { + .name = (char *[]){"Rs"}, + .num_alias = 0, + .description = "Ohmic res", + .units = "Ohm", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"Is"}, + .num_alias = 0, + .description = "Saturation current", + .units = "A", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"zetars"}, + .num_alias = 0, + .description = "Temperature coefficient of ohmic res", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"N"}, + .num_alias = 0, + .description = "Emission coefficient", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"Cj0"}, + .num_alias = 0, + .description = "Junction capacitance", + .units = "F", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"Vj"}, + .num_alias = 0, + .description = "Junction potential", + .units = "V", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"M"}, + .num_alias = 0, + .description = "Grading coefficient", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"Rth"}, + .num_alias = 0, + .description = "Thermal resistance", + .units = "K/W", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"zetarth"}, + .num_alias = 0, + .description = "Temperature coefficient of thermal res", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"zetais"}, + .num_alias = 0, + .description = "Temperature coefficient of Is", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, + { + .name = (char *[]){"Tnom"}, + .num_alias = 0, + .description = "Reference temperature", + .units = "", + .flags = PARA_TY_REAL | PARA_KIND_MODEL, + .len = 0, + }, +}; + +// fill exported data +uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR; +uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR; +uint32_t OSDI_NUM_DESCRIPTORS = 1; +// this is the main structure used by simulators, it gives access to all +// information in a model +OsdiDescriptor OSDI_DESCRIPTORS[1] = {{ + // metadata + .name = "diode_va", + + // nodes + .num_nodes = NUM_NODES, + .num_terminals = 3, + .nodes = (OsdiNode *)&nodes, + + // matrix entries + .num_jacobian_entries = NUM_MATRIX, + .jacobian_entries = (OsdiNodePair *)&jacobian_entries, + .const_jacobian_entries = (bool *)&const_jacobian_entries, + + // memory + .instance_size = sizeof(DiodeInstace), + .model_size = sizeof(DiodeModel), + .residual_resist_offset = offsetof(DiodeInstace, rhs_resist), + .residual_react_offset = offsetof(DiodeInstace, rhs_react), + .node_mapping_offset = offsetof(DiodeInstace, node_off), + .jacobian_resist_offset = offsetof(DiodeInstace, jacobian_resist), + .jacobian_react_offset = offsetof(DiodeInstace, jacobian_react), + .jacobian_ptr_resist_offset = offsetof(DiodeInstace, jacobian_ptr_resist), + .jacobian_ptr_react_offset = offsetof(DiodeInstace, jacobian_ptr_react), + + // node collapsing + .num_collapsible = NUM_COLLAPSIBLE, + .collapsible = collapsible, + .is_collapsible_offset = offsetof(DiodeInstace, is_collapsible), + + // noise + .noise_sources = NULL, + .num_noise_src = 0, + + // parameters and op vars + .num_params = NUM_PARAMS, + .num_instance_params = 1, + .num_opvars = 0, + .param_opvar = (OsdiParamOpvar *)¶ms, + + // setup + .access = &osdi_access, + .setup_model = &setup_model, + .setup_instance = &setup_instance, + .eval = &eval, + .load_noise = &load_noise, + .load_residual_resist = &load_residual_resist, + .load_residual_react = &load_residual_react, + .load_spice_rhs_dc = &load_spice_rhs_dc, + .load_spice_rhs_tran = &load_spice_rhs_tran, + .load_jacobian_resist = &load_jacobian_resist, + .load_jacobian_react = &load_jacobian_react, + .load_jacobian_tran = &load_jacobian_tran, +}}; diff --git a/test_cases/node_collapsing/netlist.sp b/test_cases/node_collapsing/netlist.sp new file mode 100644 index 000000000..acd8bc3ad --- /dev/null +++ b/test_cases/node_collapsing/netlist.sp @@ -0,0 +1,46 @@ +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=1 rs=0 cj0=1e-15 vj=0.5 m=0.6 ) +.model dmod_osdi diode_va rs=0 is=1e-13 n=1.05 Rth=0 cj0=1e-15 vj=0.5 m=0.6 + +*OSDI Diode: +*OSDI_ACTIVATE*A1 D 0 dmod_osdi + +*Built-in Diode: +*BUILT_IN_ACTIVATE*D1 D 0 T dmod_built_in + + +.control +pre_osdi diode.osdi + +set filetype=ascii +set wr_vecnames +set wr_singlescale + +* a DC sweep from 0.3V to 1V +dc Vd 0.3 0.6 0.01 +wrdata dc_sim.ngspice v(d) i(vsense) + +* 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 diff --git a/test_cases/node_collapsing/test_diode.py b/test_cases/node_collapsing/test_diode.py new file mode 100644 index 000000000..486b0e51e --- /dev/null +++ b/test_cases/node_collapsing/test_diode.py @@ -0,0 +1,148 @@ +""" test OSDI simulation of diode +""" +import os, shutil +import numpy as np +import pandas as pd +import sys +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) + +from testing import prepare_test + +# 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.osdi /usr/local/share/ngspice/osdi/diode_osdi.osdi +# +# 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. + +directory = os.path.dirname(__file__) + + +def test_ngspice(): + dir_osdi, dir_built_in = prepare_test(directory) + + # 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) #ngspice diode doesnt work with node collapsing :D + + 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() + 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", + ) + ax1.set_ylabel(r"$I_{\mathrm{D}} (\mathrm{mA})$") + 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("$f(\\mathrm{H})$") + plt.ylabel("$\\Re \\left\{ 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("$f(\\mathrm{H})$") + plt.ylabel("$\\Im\\left\{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/resistor/netlist.sp b/test_cases/resistor/netlist.sp index b960769eb..5325b0345 100644 --- a/test_cases/resistor/netlist.sp +++ b/test_cases/resistor/netlist.sp @@ -12,7 +12,7 @@ Vsense Dx D DC 0 *OSDI Resistor: *OSDI_ACTIVATE*A1 D 0 rmod_osdi -*Built-in Capacitor: +*Built-in Resistor: *BUILT_IN_ACTIVATE*R1 D 0 10 diff --git a/test_cases/resistor/resistor.c b/test_cases/resistor/resistor.c index 6daf45017..393f3824b 100644 --- a/test_cases/resistor/resistor.c +++ b/test_cases/resistor/resistor.c @@ -25,7 +25,7 @@ extern OsdiDescriptor OSDI_DESCRIPTORS[1]; // number of nodes and definitions of node ids for nicer syntax in this file // note: order should be same as "nodes" list defined later -#define NUM_NODES 3 +#define NUM_NODES 2 #define P 0 #define M 1