diff --git a/test_cases/capacitor/capacitor.c b/test_cases/capacitor/capacitor.c new file mode 100644 index 000000000..93599883d --- /dev/null +++ b/test_cases/capacitor/capacitor.c @@ -0,0 +1,367 @@ +/* + * 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/capacitor/netlist.sp b/test_cases/capacitor/netlist.sp index f59e48d54..973fcdf26 100644 --- a/test_cases/capacitor/netlist.sp +++ b/test_cases/capacitor/netlist.sp @@ -7,7 +7,7 @@ VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M) Vsense Dx D DC 0 * model definitions: -.model cmod_osdi osdi capacitor_va c=5e-12 +.model cmod_osdi capacitor_va c=5e-12 *OSDI Capacitor: *OSDI_ACTIVATE*A1 D 0 cmod_osdi @@ -17,6 +17,7 @@ Vsense Dx D DC 0 .control +pre_osdi capacitor.osdi set filetype=ascii set wr_vecnames set wr_singlescale diff --git a/test_cases/capacitor/test_capacitor.py b/test_cases/capacitor/test_capacitor.py index 1c4d467b2..be1fc38dc 100644 --- a/test_cases/capacitor/test_capacitor.py +++ b/test_cases/capacitor/test_capacitor.py @@ -2,10 +2,12 @@ """ import os, shutil import numpy as np -import subprocess import pandas as pd +import sys +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) -directory = os.path.dirname(__file__) +from testing import prepare_test # This test runs a DC, AC and Transient Simulation of a simple capacitor. # The capacitor is available as a C file and needs to be compiled to a shared object @@ -17,78 +19,12 @@ directory = os.path.dirname(__file__) # The integration test proves the functioning of the OSDI interface. # Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation. - -def create_shared_object(): - # place the file "capacitor_va.c" next to this file - subprocess.run( - [ - "gcc", - "-c", - "-Wall", - "-I", - "../../src/spicelib/devices/osdi/", - "-fpic", - "capacitor_va.c", - "-ggdb", - ], - cwd=directory, - ) - subprocess.run( - ["gcc", "-shared", "-o", "capacitor_va.so", "capacitor_va.o", "-ggdb"], - cwd=directory, - ) - os.makedirs(os.path.join(directory, "test_osdi", "osdi"), exist_ok=True) - subprocess.run( - ["mv", "capacitor_va.so", "test_osdi/osdi/capacitor_va.so"], cwd=directory - ) - subprocess.run(["rm", "capacitor_va.o"], cwd=directory) - - -# specify location of Ngspice executable to be tested -ngspice_path = os.path.join(directory, "../../debug/src/ngspice") -ngspice_path = os.path.abspath(ngspice_path) +directory = os.path.dirname(__file__) def test_ngspice(): - path_netlist = os.path.join(directory, "netlist.sp") - - # open netlist and activate Ngspice capacitor - 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") - # remove old results: - for directory_i in [dir_osdi, dir_built_in]: - shutil.rmtree(directory_i, ignore_errors=True) - - for directory_i in [dir_osdi, dir_built_in]: - os.makedirs(directory_i, exist_ok=True) - - create_shared_object() - - # 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, - ) - + 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_osdi, "dc_sim.ngspice"), sep="\\s+") diff --git a/test_cases/diode/test_diode.py b/test_cases/diode/test_diode.py index d54ff4d4c..f49adf598 100644 --- a/test_cases/diode/test_diode.py +++ b/test_cases/diode/test_diode.py @@ -2,10 +2,12 @@ """ import os, shutil import numpy as np -import subprocess import pandas as pd +import sys +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) -directory = os.path.dirname(__file__) +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 @@ -18,74 +20,11 @@ directory = os.path.dirname(__file__) # 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. - -def create_shared_object(): - # place the file "diode.c" next to this file - subprocess.run( - [ - "gcc", - "-c", - "-Wall", - "-I", - "../../src/osdi/", - "-fpic", - "diode.c", - "-ggdb", - ], - cwd=directory, - ) - subprocess.run( - ["gcc", "-shared", "-o", "diode.osdi", "diode.o", "-ggdb"], - cwd=directory, - ) - subprocess.run(["mv", "diode.osdi", "test_osdi/diode.osdi"], cwd=directory) - subprocess.run(["rm", "diode.o"], cwd=directory) - - -# specify location of Ngspice executable to be tested -ngspice_path = os.path.join(directory, "../../debug/src/ngspice") -ngspice_path = os.path.abspath(ngspice_path) +directory = os.path.dirname(__file__) def test_ngspice(): - 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") - # remove old results: - for directory_i in [dir_osdi, dir_built_in]: - shutil.rmtree(directory_i, ignore_errors=True) - - for directory_i in [dir_osdi, dir_built_in]: - os.makedirs(directory_i, exist_ok=True) - - create_shared_object() - - # 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, - ) + 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+") diff --git a/test_cases/diode/test_osdi.zip b/test_cases/diode/test_osdi.zip new file mode 100644 index 000000000..5f5690adb Binary files /dev/null and b/test_cases/diode/test_osdi.zip differ diff --git a/test_cases/multiple_devices/netlist.sp b/test_cases/multiple_devices/netlist.sp index 795a353b5..0d7c4f097 100644 --- a/test_cases/multiple_devices/netlist.sp +++ b/test_cases/multiple_devices/netlist.sp @@ -1,4 +1,4 @@ -OSDI Resistor Test +OSDI Multiple Devices Test .options abstol=1e-15 @@ -7,15 +7,17 @@ VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M) Vsense Dx D DC 0 * model definitions: -.model rmod_osdi resistor_va r=10 +.model rmod_osdi resistor_va r=20 .model cmod_osdi capacitor_va r=5e-12 *OSDI Resistor and Capacitor: *OSDI_ACTIVATE*A1 D 0 rmod_osdi -*OSDI_ACTIVATE*A2 D 0 cmod_osdi +*OSDI_ACTIVATE*A2 D 0 rmod_osdi +*OSDI_ACTIVATE*A3 D 0 cmod_osdi *Built-in Capacitor and Resistor: -*BUILT_IN_ACTIVATE*R1 D 0 10 +*BUILT_IN_ACTIVATE*R1 D 0 20 +*BUILT_IN_ACTIVATE*R2 D 0 20 *BUILT_IN_ACTIVATE*C1 D 0 5e-12 diff --git a/test_cases/multiple_devices/test_multiple.py b/test_cases/multiple_devices/test_multiple.py index f96fa6024..41ee84142 100644 --- a/test_cases/multiple_devices/test_multiple.py +++ b/test_cases/multiple_devices/test_multiple.py @@ -2,10 +2,12 @@ """ import os, shutil import numpy as np -import subprocess import pandas as pd +import sys +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) -directory = os.path.dirname(__file__) +from testing import prepare_test # This test runs a DC, AC and Transient Simulation of a simple resistor and resistor. # The capacitor and resistor are available as C files and need to be compiled to a shared objects @@ -20,101 +22,31 @@ directory = os.path.dirname(__file__) # Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation. -def create_shared_object(): - # place the file "resistor.c" and "capacitor.c" next to this file - for dev in ["capacitor", "resistor"]: - subprocess.run( - [ - "gcc", - "-c", - "-Wall", - "-I", - "../../src/osdi/", - "-fpic", - dev + ".c", - "-ggdb", - ], - cwd=directory, - ) - subprocess.run( - ["gcc", "-shared", "-o", dev + ".osdi", dev + ".o", "-ggdb"], - cwd=directory, - ) - subprocess.run( - ["mv", dev + ".osdi", "test_osdi/" + dev + ".osdi"], cwd=directory - ) - subprocess.run(["rm", dev + ".o"], cwd=directory) - - -# specify location of Ngspice executable to be tested -ngspice_path = os.path.join(directory, "../../debug/src/ngspice") -ngspice_path = os.path.abspath(ngspice_path) - +directory = os.path.dirname(__file__) def test_ngspice(): - path_netlist = os.path.join(directory, "netlist.sp") - - # open netlist and activate Ngspice devices - 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") - # remove old results: - for directory_i in [dir_osdi, dir_built_in]: - shutil.rmtree(directory_i, ignore_errors=True) - - for directory_i in [dir_osdi, dir_built_in]: - os.makedirs(directory_i, exist_ok=True) - - create_shared_object() - - # 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, - ) + 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_osdi, "dc_sim.ngspice"), sep="\\s+") - # dc_data_built_in = pd.read_csv( - # os.path.join(dir_built_in, "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_osdi["i(vsense)"].to_numpy() - # id_built_in = dc_data_built_in["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_osdi, "ac_sim.ngspice"), sep="\\s+") - # ac_data_built_in = pd.read_csv( - # os.path.join(dir_built_in, "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_osdi, "tr_sim.ngspice"), sep="\\s+") - # tr_data_built_in = pd.read_csv( - # os.path.join(dir_built_in, "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() diff --git a/test_cases/resistor/netlist.sp b/test_cases/resistor/netlist.sp index 5c466ee19..b960769eb 100644 --- a/test_cases/resistor/netlist.sp +++ b/test_cases/resistor/netlist.sp @@ -7,7 +7,7 @@ VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M) Vsense Dx D DC 0 * model definitions: -.model rmod_osdi osdi resistor_va r=10 +.model rmod_osdi resistor_va r=10 *OSDI Resistor: *OSDI_ACTIVATE*A1 D 0 rmod_osdi @@ -17,6 +17,8 @@ Vsense Dx D DC 0 .control +pre_osdi resistor.osdi + set filetype=ascii set wr_vecnames set wr_singlescale diff --git a/test_cases/resistor/resistor_va.c b/test_cases/resistor/resistor.c similarity index 100% rename from test_cases/resistor/resistor_va.c rename to test_cases/resistor/resistor.c diff --git a/test_cases/resistor/test_resistor.py b/test_cases/resistor/test_resistor.py index 9542f1967..06810b7b2 100644 --- a/test_cases/resistor/test_resistor.py +++ b/test_cases/resistor/test_resistor.py @@ -5,7 +5,12 @@ import numpy as np import subprocess import pandas as pd -directory = os.path.dirname(__file__) +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 resistor. # The capacitor is available as a C file and needs to be compiled to a shared object @@ -17,102 +22,32 @@ directory = os.path.dirname(__file__) # The integration test proves the functioning of the OSDI interface. # Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation. - -def create_shared_object(): - # place the file "resistor_va.c" next to this file - subprocess.run( - [ - "gcc", - "-c", - "-Wall", - "-I", - "../../src/spicelib/devices/osdi/", - "-fpic", - "resistor_va.c", - "-ggdb", - ], - cwd=directory, - ) - subprocess.run( - ["gcc", "-shared", "-o", "resistor_va.so", "resistor_va.o", "-ggdb"], - cwd=directory, - ) - os.makedirs(os.path.join(directory, "test_osdi", "osdi"), exist_ok=True) - subprocess.run( - ["mv", "resistor_va.so", "test_osdi/osdi/resistor_va.so"], cwd=directory - ) - subprocess.run(["rm", "resistor_va.o"], cwd=directory) - - -# specify location of Ngspice executable to be tested -ngspice_path = os.path.join(directory, "../../debug/src/ngspice") -ngspice_path = os.path.abspath(ngspice_path) +directory = os.path.dirname(__file__) def test_ngspice(): - path_netlist = os.path.join(directory, "netlist.sp") - - # open netlist and activate Ngspice resistor - 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") - # remove old results: - for directory_i in [dir_osdi, dir_built_in]: - shutil.rmtree(directory_i, ignore_errors=True) - - for directory_i in [dir_osdi, dir_built_in]: - os.makedirs(directory_i, exist_ok=True) - - create_shared_object() - - # 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, - ) + 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_osdi, "dc_sim.ngspice"), sep="\\s+") - # dc_data_built_in = pd.read_csv( - # os.path.join(dir_built_in, "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_osdi["i(vsense)"].to_numpy() - # id_built_in = dc_data_built_in["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_osdi, "ac_sim.ngspice"), sep="\\s+") - # ac_data_built_in = pd.read_csv( - # os.path.join(dir_built_in, "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_osdi, "tr_sim.ngspice"), sep="\\s+") - # tr_data_built_in = pd.read_csv( - # os.path.join(dir_built_in, "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() diff --git a/test_cases/testing.py b/test_cases/testing.py new file mode 100644 index 000000000..a131d477d --- /dev/null +++ b/test_cases/testing.py @@ -0,0 +1,94 @@ +#this file defines some common routines used by the OSDI test cases +import subprocess +import os +import shutil +import glob +from pathlib import Path + +# specify location of Ngspice executable to be tested +directory_testing = os.path.dirname(__file__) +ngspice_path = os.path.join(directory_testing, "../debug/src/ngspice") +ngspice_path = os.path.abspath(ngspice_path) + +def create_shared_objects(directory): + c_files = [] + for c_file in glob.glob(directory + "/*.c"): + basename = Path(c_file).stem + c_files.append(basename) + + for c_file in c_files: + subprocess.run( + [ + "gcc", + "-c", + "-Wall", + "-I", + "../../src/osdi/", + "-fpic", + c_file + ".c", + "-ggdb", + ], + cwd=directory, + ) + subprocess.run( + ["gcc", "-shared", "-o", c_file + ".osdi", c_file + ".o", "-ggdb"], + cwd=directory, + ) + subprocess.run( + ["mv", c_file + ".osdi", "test_osdi/" + c_file + ".osdi"], cwd=directory + ) + subprocess.run(["rm", c_file + ".o"], cwd=directory) + +def prepare_dirs(directory): + # 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]: + # remove old results + shutil.rmtree(directory_i, ignore_errors=True) + # make new directories + os.makedirs(directory_i, exist_ok=True) + + + return dir_osdi, dir_built_in + +def prepare_netlists(directory): + path_netlist = os.path.join(directory, "netlist.sp") + + # directories for test cases + dir_osdi = os.path.join(directory, "test_osdi") + dir_built_in = os.path.join(directory, "test_built_in") + + # open netlist and activate Ngspice devices + 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*", "") + + # 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) + +def run_simulations(dirs): + for dir_i in dirs: + subprocess.run( + [ + ngspice_path, + "netlist.sp", + "-b", + ], + cwd=dir_i, + ) + +def prepare_test(directory): + dir_osdi, dir_built_in = prepare_dirs(directory) + create_shared_objects(directory) + prepare_netlists(directory) + run_simulations([dir_osdi, dir_built_in]) + + return dir_osdi, dir_built_in