(testing) add res, cap and multiple devices test
This commit is contained in:
parent
daee9857ed
commit
4036e4ec26
|
|
@ -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 <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// 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,
|
||||
}};
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
OSDI Capacitor 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
|
||||
|
||||
* model definitions:
|
||||
.model cmod_osdi osdi capacitor_va c=5e-12
|
||||
|
||||
*OSDI Capacitor:
|
||||
*OSDI_ACTIVATE*A1 D 0 cmod_osdi
|
||||
|
||||
*Built-in Capacitor:
|
||||
*BUILT_IN_ACTIVATE*C1 D 0 5e-12
|
||||
|
||||
|
||||
.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)
|
||||
|
||||
* 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
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
""" test OSDI simulation of capacitor
|
||||
"""
|
||||
import os, shutil
|
||||
import numpy as np
|
||||
import subprocess
|
||||
import pandas as pd
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
|
||||
# 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
|
||||
# and then bet put into /usr/local/share/ngspice/osdi:
|
||||
#
|
||||
# > make osdi_capacitor
|
||||
# > cp capacitor_osdi.so /usr/local/share/ngspice/osdi/capacitor_osdi.so
|
||||
#
|
||||
# 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)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
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()
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
# 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()
|
||||
ax1.plot(
|
||||
dc_data_built_in["v(d)"],
|
||||
dc_data_built_in["i(vsense)"] * 1e3,
|
||||
label="built-in",
|
||||
linestyle=" ",
|
||||
marker="x",
|
||||
)
|
||||
ax1.plot(
|
||||
dc_data_osdi["v(d)"],
|
||||
dc_data_osdi["i(vsense)"] * 1e3,
|
||||
label="OSDI",
|
||||
)
|
||||
ax1.set_ylabel(r"$I_{\mathrm{P}} (\mathrm{mA})$")
|
||||
ax1.set_xlabel(r"$V_{\mathrm{PM}}(\mathrm{V})$")
|
||||
plt.legend()
|
||||
|
||||
# AC Plot
|
||||
omega = 2 * np.pi * ac_data_osdi["frequency"]
|
||||
z_analytical = 5e-12 * omega
|
||||
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"] * 1e12 / omega,
|
||||
label="built-in",
|
||||
linestyle=" ",
|
||||
marker="x",
|
||||
)
|
||||
plt.semilogx(
|
||||
ac_data_osdi["frequency"],
|
||||
ac_data_osdi["i(vsense).1"] * 1e12 / omega,
|
||||
label="OSDI",
|
||||
)
|
||||
plt.semilogx(
|
||||
ac_data_osdi["frequency"],
|
||||
np.ones_like(ac_data_osdi["frequency"]) * z_analytical * 1e12 / omega,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
plt.ylim(1, 9)
|
||||
plt.xlabel("$f(\\mathrm{H})$")
|
||||
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{pF})$")
|
||||
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()
|
||||
|
|
@ -20,7 +20,7 @@ directory = os.path.dirname(__file__)
|
|||
|
||||
|
||||
def create_shared_object():
|
||||
# plave the file "diode_va.c" next to this file
|
||||
# place the file "diode_va.c" next to this file
|
||||
subprocess.run(
|
||||
[
|
||||
"gcc",
|
||||
|
|
|
|||
|
|
@ -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 <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// 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,
|
||||
}};
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
OSDI Resistor 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
|
||||
|
||||
* model definitions:
|
||||
.model rmod_osdi osdi resistor_va r=10
|
||||
.model cmod_osdi osdi capacitor_va r=5e-12
|
||||
|
||||
*OSDI Resistor and Capacitor:
|
||||
*OSDI_ACTIVATE*A1 D 0 rmod_osdi
|
||||
*OSDI_ACTIVATE*A1 D 0 cmod_osdi
|
||||
|
||||
*Built-in Capacitor and Resistor:
|
||||
*BUILT_IN_ACTIVATE*R1 D 0 10
|
||||
*BUILT_IN_ACTIVATE*C1 D 0 5e-12
|
||||
|
||||
|
||||
.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)
|
||||
|
||||
* 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
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* 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 <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// 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 ResistorModel
|
||||
{
|
||||
double R;
|
||||
bool R_given;
|
||||
} ResistorModel;
|
||||
|
||||
// The instace structure for the diode
|
||||
typedef struct ResistorInstance
|
||||
{
|
||||
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];
|
||||
} ResistorInstance;
|
||||
|
||||
// implementation of the access function as defined by the OSDI spec
|
||||
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
|
||||
{
|
||||
ResistorModel *model = (ResistorModel *)model_;
|
||||
ResistorInstance *inst = (ResistorInstance *)inst_;
|
||||
|
||||
bool *given;
|
||||
void *value;
|
||||
|
||||
switch (id) // id of params defined in param_opvar array
|
||||
{
|
||||
case 0:
|
||||
value = (void *)&model->R;
|
||||
given = &model->R_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_)
|
||||
{
|
||||
ResistorModel *model = (ResistorModel *)model_;
|
||||
|
||||
// set parameters and check bounds
|
||||
if (!model->R_given)
|
||||
{
|
||||
model->R = 1;
|
||||
}
|
||||
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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)inst_;
|
||||
ResistorModel *model = (ResistorModel *)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)
|
||||
{
|
||||
ResistorModel *model = (ResistorModel *)model_;
|
||||
ResistorInstance *inst = (ResistorInstance *)inst_;
|
||||
|
||||
// get voltages
|
||||
double vp = prev_solve[inst->node_off[P]];
|
||||
double vm = prev_solve[inst->node_off[M]];
|
||||
|
||||
double vpm = vp - vm;
|
||||
|
||||
double ir = vpm / model->R;
|
||||
double g = 1 / model->R;
|
||||
|
||||
////////////////
|
||||
// write rhs
|
||||
////////////////
|
||||
|
||||
if (flags & CALC_RESIST_RESIDUAL)
|
||||
{
|
||||
// write resist rhs
|
||||
inst->rhs_resist[P] = ir;
|
||||
inst->rhs_resist[M] = -ir;
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// write Jacobian
|
||||
//////////////////
|
||||
|
||||
if (flags & CALC_RESIST_JACOBIAN)
|
||||
{
|
||||
// stamp resistor
|
||||
inst->jacobian_resist[P_P] = g;
|
||||
inst->jacobian_resist[P_M] = -g;
|
||||
inst->jacobian_resist[M_P] = -g;
|
||||
inst->jacobian_resist[M_M] = g;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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_)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
|
||||
ResistorInstance *inst = (ResistorInstance *)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 = false},
|
||||
{.name = "M", .units = "V", .is_reactive = false},
|
||||
};
|
||||
|
||||
// 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 *[]){"R"},
|
||||
.num_alias = 0,
|
||||
.description = "Resistance",
|
||||
.units = "Ohm",
|
||||
.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 = "resistor_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(ResistorInstance),
|
||||
.model_size = sizeof(ResistorModel),
|
||||
.residual_resist_offset = offsetof(ResistorInstance, rhs_resist),
|
||||
.residual_react_offset = offsetof(ResistorInstance, rhs_react),
|
||||
.node_mapping_offset = offsetof(ResistorInstance, node_off),
|
||||
.jacobian_resist_offset = offsetof(ResistorInstance, jacobian_resist),
|
||||
.jacobian_react_offset = offsetof(ResistorInstance, jacobian_react),
|
||||
.jacobian_ptr_resist_offset = offsetof(ResistorInstance, jacobian_ptr_resist),
|
||||
.jacobian_ptr_react_offset = offsetof(ResistorInstance, 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,
|
||||
}};
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
""" test OSDI simulation of resistor and capacitor
|
||||
"""
|
||||
import os, shutil
|
||||
import numpy as np
|
||||
import subprocess
|
||||
import pandas as pd
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
|
||||
# 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
|
||||
# and then bet put into /usr/local/share/ngspice/osdi:
|
||||
#
|
||||
# > make osdi_resistor
|
||||
# > cp resistor_osdi.so /usr/local/share/ngspice/osdi/resistor_osdi.so
|
||||
# > make osdi_capacitor
|
||||
# > cp capacitor_osdi.so /usr/local/share/ngspice/osdi/capacitor_osdi.so
|
||||
#
|
||||
# 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" and "capacitor_va.c" next to this file
|
||||
for dev in ["capacitor", "resistor"]:
|
||||
subprocess.run(
|
||||
[
|
||||
"gcc",
|
||||
"-c",
|
||||
"-Wall",
|
||||
"-I",
|
||||
"../../src/spicelib/devices/osdi/",
|
||||
"-fpic",
|
||||
dev + "_va.c",
|
||||
"-ggdb",
|
||||
],
|
||||
cwd=directory,
|
||||
)
|
||||
subprocess.run(
|
||||
["gcc", "-shared", "-o", dev + "_va.so", dev + "_va.o", "-ggdb"],
|
||||
cwd=directory,
|
||||
)
|
||||
os.makedirs(os.path.join(directory, "test_osdi", "osdi"), exist_ok=True)
|
||||
subprocess.run(
|
||||
["mv", dev + "_va.so", "test_osdi/osdi/" + dev + "_va.so"], cwd=directory
|
||||
)
|
||||
subprocess.run(["rm", dev + "_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)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
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()
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
# 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()
|
||||
ax1.plot(
|
||||
dc_data_built_in["v(d)"],
|
||||
dc_data_built_in["i(vsense)"] * 1e3,
|
||||
label="built-in",
|
||||
linestyle=" ",
|
||||
marker="x",
|
||||
)
|
||||
ax1.plot(
|
||||
dc_data_built_in["v(d)"],
|
||||
dc_data_built_in["v(d)"] / 10 * 1e3,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
ax1.plot(
|
||||
dc_data_osdi["v(d)"],
|
||||
dc_data_osdi["i(vsense)"] * 1e3,
|
||||
label="OSDI",
|
||||
)
|
||||
ax1.set_ylabel(r"$I_{\mathrm{P}} (\mathrm{mA})$")
|
||||
ax1.set_xlabel(r"$V_{\mathrm{PM}}(\mathrm{V})$")
|
||||
plt.legend()
|
||||
|
||||
# AC Plot
|
||||
omega = 2 * np.pi * ac_data_osdi["frequency"]
|
||||
z_analytical = 1 / 10
|
||||
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_built_in["frequency"],
|
||||
np.ones_like(ac_data_built_in["frequency"]) * z_analytical * 1e3,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
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"] * 1e12 / omega,
|
||||
label="built-in",
|
||||
linestyle=" ",
|
||||
marker="x",
|
||||
)
|
||||
plt.semilogx(
|
||||
ac_data_built_in["frequency"],
|
||||
np.zeros_like(ac_data_built_in["i(vsense).1"]) * 1e12 / omega,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
plt.semilogx(
|
||||
ac_data_osdi["frequency"],
|
||||
ac_data_osdi["i(vsense).1"] * 1e12 / omega,
|
||||
label="OSDI",
|
||||
)
|
||||
plt.ylim(-1, 1)
|
||||
plt.xlabel("$f(\\mathrm{H})$")
|
||||
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{pF})$")
|
||||
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_built_in["time"] * 1e9,
|
||||
tr_data_built_in["v(d)"] / 10 * 1e3,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
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()
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
OSDI Resistor 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
|
||||
|
||||
* model definitions:
|
||||
.model rmod_osdi osdi resistor_va r=10
|
||||
|
||||
*OSDI Resistor:
|
||||
*OSDI_ACTIVATE*A1 D 0 rmod_osdi
|
||||
|
||||
*Built-in Capacitor:
|
||||
*BUILT_IN_ACTIVATE*R1 D 0 10
|
||||
|
||||
|
||||
.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)
|
||||
|
||||
* 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
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* 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 <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// 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 ResistorModel
|
||||
{
|
||||
double R;
|
||||
bool R_given;
|
||||
} ResistorModel;
|
||||
|
||||
// The instace structure for the diode
|
||||
typedef struct ResistorInstance
|
||||
{
|
||||
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];
|
||||
} ResistorInstance;
|
||||
|
||||
// implementation of the access function as defined by the OSDI spec
|
||||
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
|
||||
{
|
||||
ResistorModel *model = (ResistorModel *)model_;
|
||||
ResistorInstance *inst = (ResistorInstance *)inst_;
|
||||
|
||||
bool *given;
|
||||
void *value;
|
||||
|
||||
switch (id) // id of params defined in param_opvar array
|
||||
{
|
||||
case 0:
|
||||
value = (void *)&model->R;
|
||||
given = &model->R_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_)
|
||||
{
|
||||
ResistorModel *model = (ResistorModel *)model_;
|
||||
|
||||
// set parameters and check bounds
|
||||
if (!model->R_given)
|
||||
{
|
||||
model->R = 1;
|
||||
}
|
||||
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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)inst_;
|
||||
ResistorModel *model = (ResistorModel *)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)
|
||||
{
|
||||
ResistorModel *model = (ResistorModel *)model_;
|
||||
ResistorInstance *inst = (ResistorInstance *)inst_;
|
||||
|
||||
// get voltages
|
||||
double vp = prev_solve[inst->node_off[P]];
|
||||
double vm = prev_solve[inst->node_off[M]];
|
||||
|
||||
double vpm = vp - vm;
|
||||
|
||||
double ir = vpm / model->R;
|
||||
double g = 1 / model->R;
|
||||
|
||||
////////////////
|
||||
// write rhs
|
||||
////////////////
|
||||
|
||||
if (flags & CALC_RESIST_RESIDUAL)
|
||||
{
|
||||
// write resist rhs
|
||||
inst->rhs_resist[P] = ir;
|
||||
inst->rhs_resist[M] = -ir;
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// write Jacobian
|
||||
//////////////////
|
||||
|
||||
if (flags & CALC_RESIST_JACOBIAN)
|
||||
{
|
||||
// stamp resistor
|
||||
inst->jacobian_resist[P_P] = g;
|
||||
inst->jacobian_resist[P_M] = -g;
|
||||
inst->jacobian_resist[M_P] = -g;
|
||||
inst->jacobian_resist[M_M] = g;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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_)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
ResistorInstance *inst = (ResistorInstance *)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)
|
||||
{
|
||||
|
||||
ResistorInstance *inst = (ResistorInstance *)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 = false},
|
||||
{.name = "M", .units = "V", .is_reactive = false},
|
||||
};
|
||||
|
||||
// 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 *[]){"R"},
|
||||
.num_alias = 0,
|
||||
.description = "Resistance",
|
||||
.units = "Ohm",
|
||||
.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 = "resistor_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(ResistorInstance),
|
||||
.model_size = sizeof(ResistorModel),
|
||||
.residual_resist_offset = offsetof(ResistorInstance, rhs_resist),
|
||||
.residual_react_offset = offsetof(ResistorInstance, rhs_react),
|
||||
.node_mapping_offset = offsetof(ResistorInstance, node_off),
|
||||
.jacobian_resist_offset = offsetof(ResistorInstance, jacobian_resist),
|
||||
.jacobian_react_offset = offsetof(ResistorInstance, jacobian_react),
|
||||
.jacobian_ptr_resist_offset = offsetof(ResistorInstance, jacobian_ptr_resist),
|
||||
.jacobian_ptr_react_offset = offsetof(ResistorInstance, 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,
|
||||
}};
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
""" test OSDI simulation of resistor
|
||||
"""
|
||||
import os, shutil
|
||||
import numpy as np
|
||||
import subprocess
|
||||
import pandas as pd
|
||||
|
||||
directory = os.path.dirname(__file__)
|
||||
|
||||
# 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
|
||||
# and then bet put into /usr/local/share/ngspice/osdi:
|
||||
#
|
||||
# > make osdi_resistor
|
||||
# > cp resistor_osdi.so /usr/local/share/ngspice/osdi/resistor_osdi.so
|
||||
#
|
||||
# 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)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
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()
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
# 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+"
|
||||
# )
|
||||
|
||||
# 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()
|
||||
ax1.plot(
|
||||
dc_data_built_in["v(d)"],
|
||||
dc_data_built_in["i(vsense)"] * 1e3,
|
||||
label="built-in",
|
||||
linestyle=" ",
|
||||
marker="x",
|
||||
)
|
||||
ax1.plot(
|
||||
dc_data_built_in["v(d)"],
|
||||
dc_data_built_in["v(d)"] / 10 * 1e3,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
ax1.plot(
|
||||
dc_data_osdi["v(d)"],
|
||||
dc_data_osdi["i(vsense)"] * 1e3,
|
||||
label="OSDI",
|
||||
)
|
||||
ax1.set_ylabel(r"$I_{\mathrm{P}} (\mathrm{mA})$")
|
||||
ax1.set_xlabel(r"$V_{\mathrm{PM}}(\mathrm{V})$")
|
||||
plt.legend()
|
||||
|
||||
# AC Plot
|
||||
omega = 2 * np.pi * ac_data_osdi["frequency"]
|
||||
z_analytical = 1 / 10
|
||||
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_built_in["frequency"],
|
||||
np.ones_like(ac_data_built_in["frequency"]) * z_analytical * 1e3,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
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"] * 1e12 / omega,
|
||||
label="built-in",
|
||||
linestyle=" ",
|
||||
marker="x",
|
||||
)
|
||||
plt.semilogx(
|
||||
ac_data_built_in["frequency"],
|
||||
np.zeros_like(ac_data_built_in["i(vsense).1"]) * 1e12 / omega,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
plt.semilogx(
|
||||
ac_data_osdi["frequency"],
|
||||
ac_data_osdi["i(vsense).1"] * 1e12 / omega,
|
||||
label="OSDI",
|
||||
)
|
||||
plt.ylim(-1, 1)
|
||||
plt.xlabel("$f(\\mathrm{H})$")
|
||||
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{pF})$")
|
||||
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_built_in["time"] * 1e9,
|
||||
tr_data_built_in["v(d)"] / 10 * 1e3,
|
||||
label="analytical",
|
||||
linestyle="--",
|
||||
marker="s",
|
||||
)
|
||||
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()
|
||||
Loading…
Reference in New Issue