You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

535 lines
18 KiB

/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/iferrmsg.h"
#include "ngspice/klu.h"
#include "ngspice/memory.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdidefs.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/*
* Handles any errors raised by the setup_instance and setup_model functions
*/
static int handle_init_info(OsdiInitInfo info, const OsdiDescriptor *descr) {
if (info.flags & (EVAL_RET_FLAG_FATAL | EVAL_RET_FLAG_FINISH)) {
return (E_PANIC);
}
if (info.num_errors == 0) {
return (OK);
}
for (uint32_t i = 0; i < info.num_errors; i++) {
OsdiInitError *err = &info.errors[i];
switch (err->code) {
case INIT_ERR_OUT_OF_BOUNDS: {
char *param = descr->param_opvar[err->payload.parameter_id].name[0];
printf("Parameter %s is out of bounds!\n", param);
break;
}
default:
printf("Unkown OSDO init error code %d!\n", err->code);
}
}
free(info.errors);
errMsg = tprintf("%i errors occurred during initalization", info.num_errors);
return (E_PRIVATE);
}
/*
* The OSDI instance data contains the `node_mapping` array.
* Here an index is stored for each node. This function initializes this array
* with its indicies {0, 1, 2, 3, .., n}.
* The node collapsing information generated by setup_instance is used to
* replace these initial indicies with those that a node is collapsed into.
* For example collapsing nodes i and j sets node_mapping[i] = j.
*
* Terminals can never be collapsed in ngspice because they are allocated by
* ngspice instead of OSDI. Therefore any node collapsing that involves nodes
* `i < connected_terminals` is ignored.
*
* @param const OsdiDescriptor *descr The OSDI descriptor
* @param void *inst The instance data connected_terminals
* @param uint32_t connected_terminals The number of terminals that are not
* internal nodes.
*
* @returns The number of nodes required after collapsing.
* */
static uint32_t collapse_nodes(const OsdiDescriptor *descr, void *inst,
uint32_t connected_terminals) {
/* access data inside instance */
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
bool *collapsed = (bool *)(((char *)inst) + descr->collapsed_offset);
/* without collapsing just return the total number of nodes */
uint32_t num_nodes = descr->num_nodes;
/* populate nodes with themselves*/
for (uint32_t i = 0; i < descr->num_nodes; i++) {
node_mapping[i] = i;
}
for (uint32_t i = 0; i < descr->num_collapsible; i++) {
/* check if the collapse hint (V(x,y) <+ 0) was executed */
if (!collapsed[i]) {
continue;
}
uint32_t from = descr->collapsible[i].node_1;
uint32_t to = descr->collapsible[i].node_2;
/* terminals created by the simulator cannot be collapsed
*/
if (node_mapping[from] < connected_terminals &&
(to == UINT32_MAX || node_mapping[to] < connected_terminals ||
node_mapping[to] == UINT32_MAX)) {
continue;
}
/* ensure that to is always the smaller node */
if (to != UINT32_MAX && node_mapping[from] < node_mapping[to]) {
uint32_t temp = from;
from = to;
to = temp;
}
from = node_mapping[from];
if (to != UINT32_MAX) {
to = node_mapping[to];
}
/* replace nodes mapped to from with to and reduce the number of nodes */
for (uint32_t j = 0; j < descr->num_nodes; j++) {
if (node_mapping[j] == from) {
node_mapping[j] = to;
} else if (node_mapping[j] > from && node_mapping[j] != UINT32_MAX) {
node_mapping[j] -= 1;
}
}
num_nodes -= 1;
}
return num_nodes;
}
/* replace node mapping local to the current instance (created by
* collapse_nodes) with global node indicies allocated with CKTmkVolt */
static void write_node_mapping(const OsdiDescriptor *descr, void *inst,
uint32_t *nodes) {
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
for (uint32_t i = 0; i < descr->num_nodes; i++) {
if (node_mapping[i] == UINT32_MAX) {
/* gnd node */
node_mapping[i] = 0;
} else {
node_mapping[i] = nodes[node_mapping[i]];
}
}
}
/* NGSPICE state vectors for an instance are always continous so we just write
* state_start .. state_start + num_state to state_idx */
static void write_state_ids(const OsdiDescriptor *descr, void *inst,
uint32_t state_start) {
uint32_t *state_idx = (uint32_t *)(((char *)inst) + descr->state_idx_off);
for (uint32_t i = 0; i < descr->num_states; i++) {
state_idx[i] = state_start + i;
}
}
static int init_matrix(SMPmatrix *matrix, const OsdiDescriptor *descr,
void *inst) {
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
double **jacobian_ptr_resist =
(double **)(((char *)inst) + descr->jacobian_ptr_resist_offset);
for (uint32_t i = 0; i < descr->num_jacobian_entries; i++) {
uint32_t equation = descr->jacobian_entries[i].nodes.node_1;
uint32_t unkown = descr->jacobian_entries[i].nodes.node_2;
equation = node_mapping[equation];
unkown = node_mapping[unkown];
double *ptr = SMPmakeElt(matrix, (int)equation, (int)unkown);
if (ptr == NULL) {
return (E_NOMEM);
}
jacobian_ptr_resist[i] = ptr;
uint32_t react_off = descr->jacobian_entries[i].react_ptr_off;
// complex number for ac analysis
if (react_off != UINT32_MAX) {
double **jacobian_ptr_react = (double **)(((char *)inst) + react_off);
*jacobian_ptr_react = ptr + 1;
}
}
return (OK);
}
int OSDIsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt,
int *states) {
OsdiInitInfo init_info;
OsdiNgspiceHandle handle;
GENmodel *gen_model;
int res = (OK);
int error;
CKTnode *tmp;
GENinstance *gen_inst;
int err;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
OsdiSimParas sim_params_ = get_simparams(ckt);
OsdiSimParas *sim_params = &sim_params_;
/* setup a temporary buffer */
uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes);
/* determine the number of states required by each instance */
int num_states = (int)descr->num_states;
for (uint32_t i = 0; i < descr->num_nodes; i++) {
if (descr->nodes[i].react_residual_off != UINT32_MAX) {
num_states += 2;
}
}
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
/* setup model parameter (setup_model)*/
handle = (OsdiNgspiceHandle){.kind = 1, .name = gen_model->GENmodName};
descr->setup_model((void *)&handle, model, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_model";
continue;
}
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
/* special handling for temperature parameters */
double temp = ckt->CKTtemp;
OsdiExtraInstData *extra_inst_data =
osdi_extra_instance_data(entry, gen_inst);
if (extra_inst_data->temp_given) {
temp = extra_inst_data->temp;
}
if (extra_inst_data->dt_given) {
temp += extra_inst_data->dt;
}
/* find number of connected ports to allow evaluation of $port_connected
* and to handle node collapsing correctly later
* */
int *terminals = (int *)(gen_inst + 1);
uint32_t connected_terminals = descr->num_terminals;
for (uint32_t i = 0; i < descr->num_terminals; i++) {
if (terminals[i] == -1) {
connected_terminals = i;
break;
}
}
/* calculate op independent data, init instance parameters and determine
which collapsing occurs*/
handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname};
descr->setup_instance((void *)&handle, inst, model, temp,
connected_terminals, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_instance";
continue;
}
/* setup the instance nodes */
uint32_t num_nodes = collapse_nodes(descr, inst, connected_terminals);
/* copy terminals */
memcpy(node_ids, gen_inst + 1, sizeof(int) * connected_terminals);
/* create internal nodes as required */
for (uint32_t i = connected_terminals; i < num_nodes; i++) {
// TODO handle currents correctly
if (descr->nodes[i].is_flow) {
error = CKTmkCur(ckt, &tmp, gen_inst->GENname, descr->nodes[i].name);
} else {
error = CKTmkVolt(ckt, &tmp, gen_inst->GENname, descr->nodes[i].name);
}
if (error)
return (error);
node_ids[i] = (uint32_t)tmp->number;
// TODO nodeset?
}
write_node_mapping(descr, inst, node_ids);
/* now that we have the node mapping we can create the matrix entries */
err = init_matrix(matrix, descr, inst);
if (err) {
return err;
}
/* reserve space in the state vector*/
gen_inst->GENstate = *states;
write_state_ids(descr, inst, (uint32_t)*states);
*states += num_states;
}
}
free(node_ids);
return res;
}
/* OSDI does not differentiate between setup and temperature update so we just
* call the setup routines again and assume that node collapsing (and therefore
* node mapping) stays the same
*/
extern int OSDItemp(GENmodel *inModel, CKTcircuit *ckt) {
OsdiInitInfo init_info;
OsdiNgspiceHandle handle;
GENmodel *gen_model;
int res = (OK);
GENinstance *gen_inst;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
OsdiSimParas sim_params_ = get_simparams(ckt);
OsdiSimParas *sim_params = &sim_params_;
for (gen_model = inModel; gen_model != NULL;
gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
handle = (OsdiNgspiceHandle){.kind = 4, .name = gen_model->GENmodName};
descr->setup_model((void *)&handle, model, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_model (OSDItemp)";
continue;
}
for (gen_inst = gen_model->GENinstances; gen_inst != NULL;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
// special handleing for temperature parameters
double temp = ckt->CKTtemp;
OsdiExtraInstData *extra_inst_data =
osdi_extra_instance_data(entry, gen_inst);
if (extra_inst_data->temp_given) {
temp = extra_inst_data->temp;
}
if (extra_inst_data->dt_given) {
temp += extra_inst_data->dt;
}
handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname};
/* find number of connected ports to allow evaluation of $port_connected
* and to handle node collapsing correctly later
* */
int *terminals = (int *)(gen_inst + 1);
uint32_t connected_terminals = descr->num_terminals;
for (uint32_t i = 0; i < descr->num_terminals; i++) {
if (terminals[i] == -1) {
connected_terminals = i;
break;
}
}
descr->setup_instance((void *)&handle, inst, model, temp,
connected_terminals, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_instance (OSDItemp)";
continue;
}
// TODO check that there are no changes in node collapse?
}
}
return res;
}
/* delete internal nodes
*/
extern int OSDIunsetup(GENmodel *inModel, CKTcircuit *ckt) {
GENmodel *gen_model;
GENinstance *gen_inst;
int num;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
for (gen_model = inModel; gen_model != NULL;
gen_model = gen_model->GENnextModel) {
for (gen_inst = gen_model->GENinstances; gen_inst != NULL;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
// reset is collapsible
bool *collapsed = (bool *)(((char *)inst) + descr->collapsed_offset);
memset(collapsed, 0, sizeof(bool) * descr->num_collapsible);
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
for (uint32_t i = 0; i < descr->num_nodes; i++) {
num = (int)node_mapping[i];
// hand coded implementations just know which nodes were collapsed
// however nodes may be collapsed multiple times so we can't easily use
// an approach like that instead we delete all nodes
// Deleting twiche with CKLdltNNum is fine (entry is already removed
// from the linked list and therefore no action is taken).
// However CKTdltNNum (rightfully) throws an error when trying to delete
// an external node. Therefore we need to check for each node that it is
// an internal node
if (ckt->prev_CKTlastNode->number &&
num > ckt->prev_CKTlastNode->number) {
CKTdltNNum(ckt, num);
}
}
}
}
return (OK);
}
#ifdef KLU
#include "ngspice/klu-binding.h"
static int init_matrix_klu(SMPmatrix *matrix, const OsdiDescriptor *descr,
void *inst, double **inst_matrix_ptrs) {
BindElement tmp;
BindElement *matched;
BindElement *bindings = matrix->SMPkluMatrix->KLUmatrixBindStructCOO;
size_t nz = (size_t)matrix->SMPkluMatrix->KLUmatrixLinkedListNZ;
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
double **jacobian_ptr_resist =
(double **)(((char *)inst) + descr->jacobian_ptr_resist_offset);
for (uint32_t i = 0; i < descr->num_jacobian_entries; i++) {
uint32_t equation = descr->jacobian_entries[i].nodes.node_1;
uint32_t unkown = descr->jacobian_entries[i].nodes.node_2;
equation = node_mapping[equation];
unkown = node_mapping[unkown];
if (equation != 0 && unkown != 0) {
tmp.COO = jacobian_ptr_resist[i];
tmp.CSC = NULL;
tmp.CSC_Complex = NULL;
matched = (BindElement *)bsearch(&tmp, bindings, nz, sizeof(BindElement),
BindCompare);
if (matched == NULL) {
printf("Ptr %p not found in BindStruct Table\n",
jacobian_ptr_resist[i]);
return (E_PANIC);
}
uint32_t react_off = descr->jacobian_entries[i].react_ptr_off;
// complex number for ac analysis
if (react_off != UINT32_MAX) {
double **jacobian_ptr_react = (double **)(((char *)inst) + react_off);
*jacobian_ptr_react = matched->CSC_Complex + 1;
}
jacobian_ptr_resist[i] = matched->CSC;
inst_matrix_ptrs[2 * i] = matched->CSC;
inst_matrix_ptrs[2 * i + 1] = matched->CSC_Complex;
}
}
return (OK);
}
static int update_matrix_klu(const OsdiDescriptor *descr, void *inst,
double **inst_matrix_ptrs, bool complex) {
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
double **jacobian_ptr_resist =
(double **)(((char *)inst) + descr->jacobian_ptr_resist_offset);
for (uint32_t i = 0; i < descr->num_jacobian_entries; i++) {
uint32_t equation = descr->jacobian_entries[i].nodes.node_1;
uint32_t unkown = descr->jacobian_entries[i].nodes.node_2;
equation = node_mapping[equation];
unkown = node_mapping[unkown];
if (equation != 0 && unkown != 0) {
jacobian_ptr_resist[i] = inst_matrix_ptrs[2 * i + complex];
}
}
return (OK);
}
int OSDIbindCSC(GENmodel *inModel, CKTcircuit *ckt) {
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
GENmodel *gen_model;
GENinstance *gen_inst;
NG_IGNORE(ckt);
/* setup a temporary buffer
uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes);*/
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
/* void *model = osdi_model_data(gen_model); unused */
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
double **matrix_ptrs = osdi_instance_matrix_ptr(entry, gen_inst);
int err = init_matrix_klu(ckt->CKTmatrix, descr, inst, matrix_ptrs);
if (err != (OK)) {
return err;
}
}
}
return (OK);
}
int OSDIupdateCSC(GENmodel *inModel, CKTcircuit *ckt, bool complex) {
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
GENmodel *gen_model;
GENinstance *gen_inst;
NG_IGNORE(ckt);
/* setup a temporary buffer
uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes);*/
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
/* void *model = osdi_model_data(gen_model); unused */
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
double **matrix_ptrs = osdi_instance_matrix_ptr(entry, gen_inst);
int err = update_matrix_klu(descr, inst, matrix_ptrs, complex);
if (err != (OK)) {
return err;
}
}
}
return (OK);
}
int OSDIbindCSCComplexToReal(GENmodel *inModel, CKTcircuit *ckt) {
return OSDIupdateCSC(inModel, ckt, false);
}
int OSDIbindCSCComplex(GENmodel *inModel, CKTcircuit *ckt) {
return OSDIupdateCSC(inModel, ckt, true);
}
#endif