This commit is contained in:
Meisam Bahadori 2026-07-30 13:52:01 +02:00 committed by Holger Vogt
parent f99b02c957
commit 01c1a472c5
28 changed files with 1458 additions and 14 deletions

View File

@ -1384,6 +1384,7 @@ AC_CONFIG_FILES([Makefile
src/spicelib/devices/mos9/Makefile
src/spicelib/devices/ndev/Makefile
src/spicelib/devices/res/Makefile
src/spicelib/devices/nport/Makefile
src/spicelib/devices/soi3/Makefile
src/spicelib/devices/sw/Makefile
src/spicelib/devices/tra/Makefile

View File

@ -88,6 +88,7 @@ DYNAMIC_DEVICELIBS = \
spicelib/devices/mos6/libmos6.la \
spicelib/devices/mos9/libmos9.la \
spicelib/devices/res/libres.la \
spicelib/devices/nport/libnport.la \
spicelib/devices/soi3/libsoi3.la \
spicelib/devices/sw/libsw.la \
spicelib/devices/txl/libtxl.la \

152
src/frontend/com_presnp.c Normal file
View File

@ -0,0 +1,152 @@
/* Enhancement-200: the `pre_snp` control command.
*
* `pre_snp <file.sNp> [module]` converts a Touchstone S-parameter file to a
* Verilog-A n-port model (via snp2va_convert, the C port of snp2va.py) and then
* invokes openvaf-r to compile it, producing <file>.osdi next to the source --
* so `pre_osdi <file>.osdi` then loads it, symmetric with the existing flow.
* The `.va`/`.osdi` are written beside the `.sNp` with the same base name.
*
* openvaf-r is located via, in order: the `openvaf` ngspice variable, the
* OPENVAF environment variable, $SPICE_LIB_DIR/openvaf-r (the prebuilt bin
* bundle keeps it there), then PATH.
*/
#include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h"
#include "ngspice/ftedefs.h"
#include "ngspice/cpextern.h"
#include "snp2va.h"
#include <sys/stat.h>
static int file_exists(const char *p)
{
struct stat st;
return stat(p, &st) == 0;
}
/* Locate the openvaf-r compiler. Returns a malloc'd string (copy()). */
static char *find_openvaf(void)
{
char var[1024];
char *e;
if (cp_getvar("openvaf", CP_STRING, var, sizeof var) && var[0])
return copy(var);
e = getenv("OPENVAF");
if (e && e[0])
return copy(e);
e = getenv("SPICE_LIB_DIR");
if (e && e[0]) {
char buf[1200];
(void) snprintf(buf, sizeof buf, "%s/openvaf-r", e);
if (file_exists(buf))
return copy(buf);
}
return copy("openvaf-r"); /* rely on PATH */
}
/* base = basename(snp) with the extension dropped; sanitized to a Verilog id. */
static void derive_module(const char *snp, char *mod, size_t modlen)
{
const char *base = strrchr(snp, '/');
#ifdef _WIN32
const char *bs = strrchr(snp, '\\');
if (bs && (!base || bs > base)) base = bs;
#endif
base = base ? base + 1 : snp;
size_t i = 0;
for (; base[i] && base[i] != '.' && i + 6 < modlen; i++) {
char c = base[i];
mod[i] = (isalnum((unsigned char) c) || c == '_') ? c : '_';
}
mod[i] = '\0';
if (i == 0 || isdigit((unsigned char) mod[0])) { /* must start with a letter */
memmove(mod + 1, mod, i + 1);
mod[0] = 'm';
}
}
/* Replace the extension of `src` with `ext` into `dst`. */
static void with_ext(const char *src, const char *ext, char *dst, size_t dstlen)
{
(void) snprintf(dst, dstlen, "%s", src);
char *dot = strrchr(dst, '.');
char *slash = strrchr(dst, '/');
if (dot && (!slash || dot > slash))
*dot = '\0';
size_t n = strlen(dst);
(void) snprintf(dst + n, dstlen - n, "%s", ext);
}
void com_pre_snp(wordlist *wl)
{
char module[256], va[1200], osdi[1200], nport[1200], msg[256], *snp, *ovf;
char *cmd;
size_t cmdlen;
int rc, native = 0;
/* optional leading backend flag: -osdi (default) or -native */
while (wl && wl->wl_word && wl->wl_word[0] == '-') {
if (eq(wl->wl_word, "-native")) native = 1;
else if (eq(wl->wl_word, "-osdi")) native = 0;
else { fprintf(cp_err, "pre_snp: unknown option '%s'\n", wl->wl_word); return; }
wl = wl->wl_next;
}
if (!wl || !wl->wl_word) {
fprintf(cp_err, "usage: pre_snp [-osdi|-native] <file.sNp> [module]\n"
" -osdi (default) Touchstone -> Verilog-A -> openvaf-r -> <file>.osdi,\n"
" then load with `pre_osdi <file>.osdi`.\n"
" -native Touchstone -> <file>.nport for the built-in n-port\n"
" device (no compiler); use it in the deck with\n"
" `N1 <ports..> <ref> m` / `.model m nport(file=\"<file>.nport\")`.\n");
return;
}
snp = wl->wl_word;
if (wl->wl_next && wl->wl_next->wl_word) {
(void) snprintf(module, sizeof module, "%s", wl->wl_next->wl_word);
} else {
derive_module(snp, module, sizeof module);
}
/* -native: emit the compact .nport fit file; no Verilog-A / openvaf-r step. */
if (native) {
with_ext(snp, ".nport", nport, sizeof nport);
if (snp2nport_convert(snp, nport, msg, sizeof msg)) {
fprintf(cp_err, "pre_snp: %s\n", msg);
return;
}
fprintf(cp_out, "pre_snp: %s -> %s (%s)\n", snp, nport, msg);
fprintf(cp_out, "pre_snp: use it with `N1 <ports..> <ref> m` and\n"
" `.model m nport(file=\"%s\")`\n", nport);
return;
}
with_ext(snp, ".va", va, sizeof va);
with_ext(snp, ".osdi", osdi, sizeof osdi);
/* 1. Touchstone -> Verilog-A (the C converter) */
if (snp2va_convert(snp, va, module, msg, sizeof msg)) {
fprintf(cp_err, "pre_snp: %s\n", msg);
return;
}
fprintf(cp_out, "pre_snp: %s -> %s (%s, module '%s')\n", snp, va, msg, module);
/* 2. compile with openvaf-r -> .osdi */
ovf = find_openvaf();
cmdlen = strlen(ovf) + strlen(va) + strlen(osdi) + 32;
cmd = TMALLOC(char, cmdlen);
(void) snprintf(cmd, cmdlen, "\"%s\" \"%s\" -o \"%s\"", ovf, va, osdi);
rc = system(cmd);
tfree(cmd);
if (rc != 0) {
fprintf(cp_err, "pre_snp: openvaf-r failed (exit %d) compiling %s.\n"
" Set the compiler with `set openvaf=/path/to/openvaf-r`, the OPENVAF\n"
" environment variable, or put openvaf-r in $SPICE_LIB_DIR or PATH.\n",
rc, va);
tfree(ovf);
return;
}
tfree(ovf);
fprintf(cp_out, "pre_snp: compiled -> %s (load it with `pre_osdi %s`)\n", osdi, osdi);
}

View File

@ -643,8 +643,16 @@ static void emit_filter(FILE *fo, cplx pole, int kind)
}
/* ============================ public API ============================ */
int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
char *msg, int msglen)
/* Shared front half: parse Touchstone, S->Y, common-pole vector fit with order
* selection, reciprocal mirror, PSD-project E. On success returns 0 and hands the
* caller freshly-owned fit arrays P[Np], res[N*N*Np], d[N*N], e[N*N] (caller frees
* them); the parse scaffolding is freed here. Layout is exactly what both emitters
* and the native `.nport` device expect: poles canonicalized (real first, then
* adjacent conjugate pairs); res indexed (i*N+j)*Np+k. Leak-tolerant for discarded
* candidate fits, as before -- a one-shot conversion tool. */
static int snp_fit(const char *snpfile, int *pN, int *pNp,
cplx **pP, cplx **pRes, double **pD, double **pE,
double *pErr, char *msg, int msglen)
{
TS ts; int i, j, k, r;
if (parse_touchstone(snpfile, &ts, msg, msglen)) return 1;
@ -748,6 +756,19 @@ int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
/* force the improper (e*s) capacitance matrix passive so transient is stable */
psd_project_E(ee, N);
*pN = N; *pNp = Np; *pP = P; *pRes = res; *pD = dd; *pE = ee; *pErr = bestErr;
free(Y); free(s); free(sn); free(F); free(elems); ts_free(&ts);
return 0;
}
/* ---------- Touchstone -> Verilog-A (structured laplace_nd realization) ---------- */
int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
char *msg, int msglen)
{
int N, Np, i, j, k;
cplx *P, *res; double *dd, *ee, bestErr;
if (snp_fit(snpfile, &N, &Np, &P, &res, &dd, &ee, &bestErr, msg, msglen)) return 1;
/* ---- emit VA (shared-pole realization; Fix #4) ----
* All N^2 elements share the SAME poles, so realize the pole-filters ONCE per
* input port and form each output current as a cheap weighted sum, instead of
@ -827,7 +848,7 @@ int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
free(svU); free(svS); free(svV);
FILE *fo = fopen(vafile, "w");
if (!fo) { snprintf(msg,(size_t)msglen,"cannot write '%s'", vafile); ts_free(&ts); return 1; }
if (!fo) { snprintf(msg,(size_t)msglen,"cannot write '%s'", vafile); return 1; }
fprintf(fo, "`include \"disciplines.vams\"\n\n");
fprintf(fo, "// Generated by pre_snp from %s\n", snpfile);
fprintf(fo, "// %d-port, %d common poles; structured realization (%d laplace_nd filters, AC + transient).\n",
@ -906,8 +927,47 @@ int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
free(chW); free(chU); free(chV); free(chMx); free(ch_lr); free(ch_r); free(ch_kind); free(ch_sec);
free(sc_pole); free(sc_kind);
snprintf(msg,(size_t)msglen,"%d-port, %d poles, rms rel err %.2e", N, Np, bestErr<1e300?bestErr:0.0);
/* frees (leak-tolerant: one-shot tool) */
free(Y); free(s); free(sn); free(F); free(elems); ts_free(&ts);
return 0;
}
/* ---------- Touchstone -> native `.nport` fit file (Enhancement-242) ----------
* Same shared parse+fit, written directly as the compact pole/residue description
* the built-in n-port device reads -- no Verilog-A, no openvaf-r compile. The
* device evaluates the identical model Y_ij(s)=d+s*e+sum_k res/(s-p_k), so a
* `-native` run reproduces a `-osdi` run to fit accuracy. */
int snp2nport_convert(const char *snpfile, const char *nportfile,
char *msg, int msglen)
{
int N, Np, i, j, k;
cplx *P, *res; double *dd, *ee, bestErr;
if (snp_fit(snpfile, &N, &Np, &P, &res, &dd, &ee, &bestErr, msg, msglen)) return 1;
FILE *fo = fopen(nportfile, "w");
if (!fo) { snprintf(msg,(size_t)msglen,"cannot write '%s'", nportfile); return 1; }
fprintf(fo, "NPORT 1\n");
fprintf(fo, "# generated by pre_snp -native from %s\n", snpfile);
fprintf(fo, "nports %d\nnpoles %d\n", N, Np);
fprintf(fo, "poles\n");
for (k = 0; k < Np; k++)
fprintf(fo, " %.15e %.15e\n", creal(P[k]), cimag(P[k]));
fprintf(fo, "d\n");
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) fprintf(fo, " %.15e", dd[i*N+j]);
fprintf(fo, "\n");
}
fprintf(fo, "e\n");
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) fprintf(fo, " %.15e", ee[i*N+j]);
fprintf(fo, "\n");
}
fprintf(fo, "res\n");
for (i = 0; i < N; i++) for (j = 0; j < N; j++)
for (k = 0; k < Np; k++) {
long idx = (long)(i*N+j)*Np + k;
fprintf(fo, " %.15e %.15e\n", creal(res[idx]), cimag(res[idx]));
}
fclose(fo);
snprintf(msg,(size_t)msglen,"%d-port, %d poles, rms rel err %.2e", N, Np, bestErr<1e300?bestErr:0.0);
return 0;
}

View File

@ -49,8 +49,14 @@ typedef double _Complex cplx;
/* ============================ linear algebra ============================ */
/* Least-squares min||A x - b|| for a REAL overdetermined system (m>=n) via
* Householder QR. A is row-major m*n, b length m, x length n. Returns 0 on ok. */
/* Least-squares min||A x - b|| for a REAL system via Householder QR. A is
* row-major m*n, b length m, x length n. Returns 0 on ok. Handles the
* underdetermined case m<n safely: the Householder sweep triangularizes only the
* first min(m,n) columns, and the back-substitution below leaves any unknown with
* no constraining row (i>=m) at zero rather than reading past the m-row A and b
* (a heap-buffer-overflow reachable from `pre_snp` on a Touchstone file with very
* few frequency points, where the vector fit's stacked system has fewer rows than
* poles). For the normal overdetermined path (m>=n) the guard never fires. */
static int lstsq_real(double *A, double *b, int m, int n, double *x)
{
int i, j, k;
@ -79,6 +85,7 @@ static int lstsq_real(double *A, double *b, int m, int n, double *x)
free(v);
}
for (i = n-1; i >= 0; i--) {
if (i >= m) { x[i] = 0.0; continue; } /* unknown i has no constraining row */
double acc = b[i];
for (j = i+1; j < n; j++) acc -= A[i*n+j]*x[j];
x[i] = (A[i*n+i] != 0.0) ? acc/A[i*n+i] : 0.0;
@ -208,6 +215,13 @@ static int parse_touchstone(const char *fn, TS *out, char *msg, int msglen)
const char *dot = strrchr(fn, '.');
if (dot && (dot[1]=='s'||dot[1]=='S') && (fn[strlen(fn)-1]=='p'||fn[strlen(fn)-1]=='P')) {
N = atoi(dot+2);
/* Enhancement-227: reject an implausible port count from the filename
* (e.g. `.s2147483647p`). N is stored in out->N and used to size the
* downstream N x N vector fit; a huge N over-allocates / overflows and
* corrupts the heap. Real Touchstone files have few ports -- above the
* brute-force limit, drop back to inferring N from the data. */
if (N > 512)
N = 0;
}
if (N <= 0) {
int c;
@ -629,8 +643,16 @@ static void emit_filter(FILE *fo, cplx pole, int kind)
}
/* ============================ public API ============================ */
int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
char *msg, int msglen)
/* Shared front half: parse Touchstone, S->Y, common-pole vector fit with order
* selection, reciprocal mirror, PSD-project E. On success returns 0 and hands the
* caller freshly-owned fit arrays P[Np], res[N*N*Np], d[N*N], e[N*N] (caller frees
* them); the parse scaffolding is freed here. Layout is exactly what both emitters
* and the native `.nport` device expect: poles canonicalized (real first, then
* adjacent conjugate pairs); res indexed (i*N+j)*Np+k. Leak-tolerant for discarded
* candidate fits, as before -- a one-shot conversion tool. */
static int snp_fit(const char *snpfile, int *pN, int *pNp,
cplx **pP, cplx **pRes, double **pD, double **pE,
double *pErr, char *msg, int msglen)
{
TS ts; int i, j, k, r;
if (parse_touchstone(snpfile, &ts, msg, msglen)) return 1;
@ -734,6 +756,19 @@ int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
/* force the improper (e*s) capacitance matrix passive so transient is stable */
psd_project_E(ee, N);
*pN = N; *pNp = Np; *pP = P; *pRes = res; *pD = dd; *pE = ee; *pErr = bestErr;
free(Y); free(s); free(sn); free(F); free(elems); ts_free(&ts);
return 0;
}
/* ---------- Touchstone -> Verilog-A (structured laplace_nd realization) ---------- */
int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
char *msg, int msglen)
{
int N, Np, i, j, k;
cplx *P, *res; double *dd, *ee, bestErr;
if (snp_fit(snpfile, &N, &Np, &P, &res, &dd, &ee, &bestErr, msg, msglen)) return 1;
/* ---- emit VA (shared-pole realization; Fix #4) ----
* All N^2 elements share the SAME poles, so realize the pole-filters ONCE per
* input port and form each output current as a cheap weighted sum, instead of
@ -813,7 +848,7 @@ int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
free(svU); free(svS); free(svV);
FILE *fo = fopen(vafile, "w");
if (!fo) { snprintf(msg,(size_t)msglen,"cannot write '%s'", vafile); ts_free(&ts); return 1; }
if (!fo) { snprintf(msg,(size_t)msglen,"cannot write '%s'", vafile); return 1; }
fprintf(fo, "`include \"disciplines.vams\"\n\n");
fprintf(fo, "// Generated by pre_snp from %s\n", snpfile);
fprintf(fo, "// %d-port, %d common poles; structured realization (%d laplace_nd filters, AC + transient).\n",
@ -892,8 +927,47 @@ int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
free(chW); free(chU); free(chV); free(chMx); free(ch_lr); free(ch_r); free(ch_kind); free(ch_sec);
free(sc_pole); free(sc_kind);
snprintf(msg,(size_t)msglen,"%d-port, %d poles, rms rel err %.2e", N, Np, bestErr<1e300?bestErr:0.0);
/* frees (leak-tolerant: one-shot tool) */
free(Y); free(s); free(sn); free(F); free(elems); ts_free(&ts);
return 0;
}
/* ---------- Touchstone -> native `.nport` fit file (Enhancement-242) ----------
* Same shared parse+fit, written directly as the compact pole/residue description
* the built-in n-port device reads -- no Verilog-A, no openvaf-r compile. The
* device evaluates the identical model Y_ij(s)=d+s*e+sum_k res/(s-p_k), so a
* `-native` run reproduces a `-osdi` run to fit accuracy. */
int snp2nport_convert(const char *snpfile, const char *nportfile,
char *msg, int msglen)
{
int N, Np, i, j, k;
cplx *P, *res; double *dd, *ee, bestErr;
if (snp_fit(snpfile, &N, &Np, &P, &res, &dd, &ee, &bestErr, msg, msglen)) return 1;
FILE *fo = fopen(nportfile, "w");
if (!fo) { snprintf(msg,(size_t)msglen,"cannot write '%s'", nportfile); return 1; }
fprintf(fo, "NPORT 1\n");
fprintf(fo, "# generated by pre_snp -native from %s\n", snpfile);
fprintf(fo, "nports %d\nnpoles %d\n", N, Np);
fprintf(fo, "poles\n");
for (k = 0; k < Np; k++)
fprintf(fo, " %.15e %.15e\n", creal(P[k]), cimag(P[k]));
fprintf(fo, "d\n");
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) fprintf(fo, " %.15e", dd[i*N+j]);
fprintf(fo, "\n");
}
fprintf(fo, "e\n");
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) fprintf(fo, " %.15e", ee[i*N+j]);
fprintf(fo, "\n");
}
fprintf(fo, "res\n");
for (i = 0; i < N; i++) for (j = 0; j < N; j++)
for (k = 0; k < Np; k++) {
long idx = (long)(i*N+j)*Np + k;
fprintf(fo, " %.15e %.15e\n", creal(res[idx]), cimag(res[idx]));
}
fclose(fo);
snprintf(msg,(size_t)msglen,"%d-port, %d poles, rms rel err %.2e", N, Np, bestErr<1e300?bestErr:0.0);
return 0;
}

View File

@ -6,5 +6,9 @@
* msg gets a one-line status/error. (No ngspice deps in the converter core.) */
int snp2va_convert(const char *snpfile, const char *vafile, const char *module,
char *msg, int msglen);
/* Enhancement-242: same parse+vector-fit, emitted as a native `.nport` fit file
* (for the built-in n-port device) instead of Verilog-A. */
int snp2nport_convert(const char *snpfile, const char *nportfile,
char *msg, int msglen);
void com_pre_snp(wordlist *wl);
#endif

View File

@ -42,6 +42,7 @@ SUBDIRS = \
mos6 \
mos9 \
res \
nport \
soi3 \
sw \
tra \
@ -104,6 +105,7 @@ DIST_SUBDIRS = \
mos9 \
ndev \
res \
nport \
soi3 \
sw \
tra \

View File

@ -117,6 +117,7 @@ extern struct coreInfo_t coreInfo; /* cmexport.c */
#include "mos9/mos9itf.h"
#include "cpl/cplitf.h"
#include "res/resitf.h"
#include "nport/nportitf.h"
#include "soi3/soi3itf.h"
#include "sw/switf.h"
#include "tra/traitf.h"
@ -184,6 +185,7 @@ static SPICEdev *(*static_devices[])(void) = {
get_mos6_info,
get_mos9_info,
get_res_info,
get_nport_info,
get_soi3_info,
get_sw_info,
get_tra_info,

View File

@ -0,0 +1,28 @@
## Process this file with automake to produce Makefile.in
## Enhancement-242: native n-port rational-model device
noinst_LTLIBRARIES = libnport.la
libnport_la_SOURCES = \
nport.c \
nportacload.c \
nportask.c \
nportbindCSC.c \
nportdefs.h \
nportdel.c \
nportext.h \
nportinit.c \
nportinit.h \
nportitf.h \
nportload.c \
nportmask.c \
nportmpar.c \
nportparam.c \
nportread.c \
nportsetup.c \
nporttemp.c
AM_CPPFLAGS = @AM_CPPFLAGS@ -I$(top_srcdir)/src/include
AM_CFLAGS = $(STATIC)
MAINTAINERCLEANFILES = Makefile.in

View File

@ -0,0 +1,26 @@
/**********
Enhancement-242: native n-port device -- parameter tables.
**********/
#include "ngspice/ngspice.h"
#include "nportdefs.h"
#include "ngspice/devdefs.h"
#include "ngspice/ifsim.h"
/* instance parameters: none (everything comes from the .model / fit file) */
IFparm NPORTpTable[] = {
OPU("nports_i", NPORT_NPORTS, IF_INTEGER, "number of ports")
};
/* model parameters */
IFparm NPORTmPTable[] = {
IP("nport", NPORT_MOD_NPORT, IF_FLAG, "native n-port rational device"),
IP("file", NPORT_MOD_FILE, IF_STRING, "path to the .nport fit file"),
OP("nports", NPORT_NPORTS, IF_INTEGER, "number of ports"),
OP("npoles", NPORT_NPOLES, IF_INTEGER, "number of poles")
};
int NPORTpTSize = NUMELEMS(NPORTpTable);
int NPORTmPTSize = NUMELEMS(NPORTmPTable);
int NPORTiSize = sizeof(NPORTinstance);
int NPORTmSize = sizeof(NPORTmodel);

View File

@ -0,0 +1,45 @@
/**********
Enhancement-242: native n-port device -- AC load.
Stamps the complex admittance Y_ij(jw) directly (multi-terminal conductance)
into the (real, imag) matrix slots -- the (ptr, ptr+1) convention used by the
RLC / OSDI devices.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
extern void NPORTadmittance(NPORTmodel *, int, int, double, double,
double *, double *);
int
NPORTacLoad(GENmodel *inModel, CKTcircuit *ckt)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
int i, j, N;
double w = ckt->CKTomega;
for (; model; model = NPORTnextModel(model)) {
N = model->NPORTnPorts;
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
double yr, yi;
NPORTadmittance(model, i, j, 0.0, w, &yr, &yi);
*(here->NPORTyPtr[i * N + j]) += yr;
*(here->NPORTyPtr[i * N + j] + 1) += yi;
*(here->NPORTyColPtr[i]) += -yr;
*(here->NPORTyColPtr[i] + 1) += -yi;
*(here->NPORTyRowPtr[j]) += -yr;
*(here->NPORTyRowPtr[j] + 1) += -yi;
*(here->NPORTyRefPtr) += yr;
*(here->NPORTyRefPtr + 1) += yi;
}
}
}
}
return OK;
}

View File

@ -0,0 +1,26 @@
/**********
Enhancement-242: native n-port device -- instance query.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "ngspice/ifsim.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
int
NPORTask(CKTcircuit *ckt, GENinstance *inst, int which,
IFvalue *value, IFvalue *select)
{
NPORTinstance *here = (NPORTinstance *)inst;
NG_IGNORE(ckt);
NG_IGNORE(select);
switch (which) {
case NPORT_NPORTS:
value->iValue = here->NPORTn;
return OK;
default:
return E_BADPARM;
}
}

View File

@ -0,0 +1,126 @@
/**********
Enhancement-242: native n-port device -- KLU CSC binding.
After the KLU reorder, re-point each stamped matrix element from its Sparse (COO)
location to the CSC slot, and support the complex<->real toggling used by AC. The
built-in devices do this with the named-field CREATE_KLU_BINDING_TABLE macros; this
device stamps through pointer ARRAYS, so the same bsearch/replace logic is applied
element-by-element here. A ground row/col (node index 0) is left unbound -- its
pointer stays the valid Sparse trash location and its stamp is harmlessly ignored,
exactly as for a grounded RLC terminal.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
#include "ngspice/klu-binding.h"
/* Bind one COO pointer to its CSC slot. row/col are the 1-based node numbers of
* this element; a 0 (ground) is skipped. Returns the matched BindElement (NULL if
* skipped or not found), and rewrites *pptr to the CSC location on success. */
static BindElement *
nport_bind(BindElement *BindStruct, size_t nz, double **pptr, int row, int col)
{
BindElement key, *matched;
if (row <= 0 || col <= 0 || *pptr == NULL)
return NULL; /* ground element: keep Sparse pointer */
key.COO = *pptr; key.CSC = NULL; key.CSC_Complex = NULL;
matched = (BindElement *) bsearch(&key, BindStruct, nz, sizeof(BindElement), BindCompare);
if (matched == NULL) {
printf("nport: Ptr %p not found in KLU bind table\n", (void *) *pptr);
return NULL;
}
*pptr = matched->CSC;
return matched;
}
int
NPORTbindCSC(GENmodel *inModel, CKTcircuit *ckt)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
BindElement *BindStruct;
size_t nz;
int i, j, N, ref;
int *node;
BindStruct = ckt->CKTmatrix->SMPkluMatrix->KLUmatrixBindStructCOO;
nz = (size_t) ckt->CKTmatrix->SMPkluMatrix->KLUmatrixLinkedListNZ;
for (; model; model = NPORTnextModel(model)) {
N = model->NPORTnPorts;
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
node = GENnode(&here->gen);
ref = here->NPORTrefNode;
for (i = 0; i < N; i++) {
here->NPORTyColBind[i] =
nport_bind(BindStruct, nz, &here->NPORTyColPtr[i], node[i], ref);
here->NPORTyRowBind[i] =
nport_bind(BindStruct, nz, &here->NPORTyRowPtr[i], ref, node[i]);
for (j = 0; j < N; j++)
here->NPORTyBind[i * N + j] =
nport_bind(BindStruct, nz, &here->NPORTyPtr[i * N + j], node[i], node[j]);
}
here->NPORTyRefBind =
nport_bind(BindStruct, nz, &here->NPORTyRefPtr, ref, ref);
}
}
return OK;
}
int
NPORTbindCSCComplex(GENmodel *inModel, CKTcircuit *ckt)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
int i, j, N;
NG_IGNORE(ckt);
for (; model; model = NPORTnextModel(model)) {
N = model->NPORTnPorts;
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
for (i = 0; i < N; i++) {
if (here->NPORTyColBind[i])
here->NPORTyColPtr[i] = here->NPORTyColBind[i]->CSC_Complex;
if (here->NPORTyRowBind[i])
here->NPORTyRowPtr[i] = here->NPORTyRowBind[i]->CSC_Complex;
for (j = 0; j < N; j++)
if (here->NPORTyBind[i * N + j])
here->NPORTyPtr[i * N + j] = here->NPORTyBind[i * N + j]->CSC_Complex;
}
if (here->NPORTyRefBind)
here->NPORTyRefPtr = here->NPORTyRefBind->CSC_Complex;
}
}
return OK;
}
int
NPORTbindCSCComplexToReal(GENmodel *inModel, CKTcircuit *ckt)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
int i, j, N;
NG_IGNORE(ckt);
for (; model; model = NPORTnextModel(model)) {
N = model->NPORTnPorts;
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
for (i = 0; i < N; i++) {
if (here->NPORTyColBind[i])
here->NPORTyColPtr[i] = here->NPORTyColBind[i]->CSC;
if (here->NPORTyRowBind[i])
here->NPORTyRowPtr[i] = here->NPORTyRowBind[i]->CSC;
for (j = 0; j < N; j++)
if (here->NPORTyBind[i * N + j])
here->NPORTyPtr[i * N + j] = here->NPORTyBind[i * N + j]->CSC;
}
if (here->NPORTyRefBind)
here->NPORTyRefPtr = here->NPORTyRefBind->CSC;
}
}
return OK;
}

View File

@ -0,0 +1,123 @@
/**********
Enhancement-242: native n-port rational-model device.
A built-in ngspice device that realizes an arbitrary-port linear block from a
pole-residue (vector-fitted) Y-parameter model produced by `pre_snp -native`:
Y_ij(s) = d_ij + s * e_ij + sum_k res_ijk / (s - p_k) (shared poles)
Stamped DIRECTLY into the sparse matrix (DC/AC/tran) in admittance / branch-current
form -- no Verilog-A / OpenVAF compile -- so it scales to hundreds of ports where
the `pre_snp -osdi` (VA->OSDI) path hits the compiler wall (~24-32 ports).
Instantiated through the generic `N` device dispatcher (inp2n.c), broadened from
OSDI-only to also accept this model type:
N1 p1 p2 ... pN ref mymodel
.model mymodel nport(file="mymodel.nport")
Port nodes are read from the generic GENnode() array (ports 0..N-1, then ref).
The fit data lives in a compact `.nport` file, written by `pre_snp -native`, and
is read into the model at temperature time.
**********/
#ifndef ngspice_NPORTDEFS_H
#define ngspice_NPORTDEFS_H
#include "ngspice/ifsim.h"
#include "ngspice/gendefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/complex.h"
#include "ngspice/klu.h" /* BindElement (KLU CSC binding) */
/* Max terminals accepted through the N dispatcher for an nport instance
* (ports + 1 reference). Sizing the generic GENnode array; instances that use
* fewer ports simply leave the rest unbound. */
#define NPORT_MAXTERMS 512
/* per-instance data */
typedef struct sNPORTinstance {
struct GENinstance gen;
/* GENnode array -- CKTbindNode() writes the port + reference node numbers
* here, and GENnode(inst) returns (int*)(inst+1), so this MUST be the very
* first member after `gen` (before any other field), sized to the device's
* terminal count (NPORT_MAXTERMS). ports are [0..N-1], reference is [N]. */
int NPORTnodeArray[NPORT_MAXTERMS];
#define NPORTmodPtr(inst) ((struct sNPORTmodel *)((inst)->gen.GENmodPtr))
#define NPORTnextInstance(inst) ((struct sNPORTinstance *)((inst)->gen.GENnextInstance))
#define NPORTname gen.GENname
#define NPORTstate gen.GENstate
int NPORTn; /* number of ports on this instance (== model N) */
int NPORTrefNode; /* reference node index (GENnode[N]) */
/* Direct admittance (multi-terminal conductance) stamp -- no branch
* currents. Port current leaving node i is I_i = sum_j Y_ij (V_j - V_ref),
* giving the four-corner stamp for each (i,j):
* (node_i, node_j) += +Y_ij (ref, node_j) += -Y_ij
* (node_i, ref) += -Y_ij (ref, ref) += +Y_ij (accumulated)
* Complex AC uses the (ptr, ptr+1) real/imag convention (RLC/OSDI style). */
double **NPORTyPtr; /* [N*N] (node_i, node_j) */
double **NPORTyColPtr; /* [N] (node_i, ref) : -sum_j Y_ij */
double **NPORTyRowPtr; /* [N] (ref, node_j) : -sum_i Y_ij */
double *NPORTyRefPtr; /* (ref, ref) : +sum_ij Y_ij */
/* KLU CSC bindings, parallel to the pointer arrays above (NULL for a
* ground row/col, which keeps its Sparse trash pointer, exactly like the
* built-in RLC devices). */
BindElement **NPORTyBind; /* [N*N] */
BindElement **NPORTyColBind; /* [N] */
BindElement **NPORTyRowBind; /* [N] */
BindElement *NPORTyRefBind; /* single */
/* transient companion state base (Phase 3) */
int NPORTstateBase;
unsigned NPORTallocated :1; /* setup arrays allocated */
} NPORTinstance;
/* per-model data */
typedef struct sNPORTmodel {
struct GENmodel gen;
#define NPORTmodType gen.GENmodType
#define NPORTnextModel(inst) ((struct sNPORTmodel *)((inst)->gen.GENnextModel))
#define NPORTinstances(inst) ((NPORTinstance *)((inst)->gen.GENinstances))
#define NPORTmodName gen.GENmodName
char *NPORTfile; /* path to the .nport fit file */
/* fit data, loaded from NPORTfile */
int NPORTnPorts; /* N */
int NPORTnPoles; /* Np (real-canonical layout: real poles first,
* conj pairs as adjacent +Im then its mate) */
double *NPORTpoleRe; /* [Np] */
double *NPORTpoleIm; /* [Np] */
double *NPORTd; /* [N*N] constant term d_ij */
double *NPORTe; /* [N*N] s-linear term e_ij */
double *NPORTresRe; /* [N*N*Np] residue real res_ijk (index (i*N+j)*Np+k) */
double *NPORTresIm; /* [N*N*Np] residue imag res_ijk */
unsigned NPORTfileGiven :1;
unsigned NPORTloaded :1;
} NPORTmodel;
/* model parameters */
enum {
NPORT_MOD_NPORT = 1, /* nport() -- the .model type flag (bare keyword) */
NPORT_MOD_FILE, /* file="..." */
NPORT_NPORTS, /* query: N */
NPORT_NPOLES /* query: Np */
};
/* load the fit file into the model (nportread.c) */
extern int NPORTreadFile(NPORTmodel *model);
#include "nportext.h"
#endif /* ngspice_NPORTDEFS_H */

View File

@ -0,0 +1,25 @@
/**********
Enhancement-242: native n-port device -- instance teardown.
Frees the heap arrays allocated in NPORTsetup.
**********/
#include "ngspice/ngspice.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
int
NPORTdelete(GENinstance *inst)
{
NPORTinstance *here = (NPORTinstance *)inst;
if (here->NPORTallocated) {
tfree(here->NPORTyPtr);
tfree(here->NPORTyColPtr);
tfree(here->NPORTyRowPtr);
tfree(here->NPORTyBind);
tfree(here->NPORTyColBind);
tfree(here->NPORTyRowBind);
here->NPORTallocated = 0;
}
return OK;
}

View File

@ -0,0 +1,19 @@
/* Enhancement-242: native n-port device -- extern declarations */
#ifndef ngspice_NPORTEXT_H
#define ngspice_NPORTEXT_H
extern int NPORTacLoad(GENmodel *, CKTcircuit *);
extern int NPORTdelete(GENinstance *);
extern int NPORTload(GENmodel *, CKTcircuit *);
extern int NPORTmParam(int, IFvalue *, GENmodel *);
extern int NPORTparam(int, IFvalue *, GENinstance *, IFvalue *);
extern int NPORTsetup(SMPmatrix *, GENmodel *, CKTcircuit *, int *);
extern int NPORTunsetup(GENmodel *, CKTcircuit *);
extern int NPORTtemp(GENmodel *, CKTcircuit *);
extern int NPORTask(CKTcircuit *, GENinstance *, int, IFvalue *, IFvalue *);
extern int NPORTmAsk(CKTcircuit *, GENmodel *, int, IFvalue *);
extern int NPORTbindCSC(GENmodel *, CKTcircuit *);
extern int NPORTbindCSCComplex(GENmodel *, CKTcircuit *);
extern int NPORTbindCSCComplexToReal(GENmodel *, CKTcircuit *);
#endif

View File

@ -0,0 +1,95 @@
/**********
Enhancement-242: native n-port device -- SPICEdev descriptor / init.
**********/
#include "ngspice/config.h"
#include <string.h>
#include <stdio.h>
#include "ngspice/devdefs.h"
#include "nportdefs.h"
#include "nportitf.h"
#include "nportinit.h"
/* Fixed maximum terminal count accepted through the N dispatcher. Sizing the
* generic GENnode array; an instance uses only its N+1 (ports + ref) nodes. */
static int NPORTnTerms = NPORT_MAXTERMS;
/* generic terminal names ("1".."NPORT_MAXTERMS"), built once on first request */
static char *NPORTnames[NPORT_MAXTERMS];
SPICEdev NPORTinfo = {
.DEVpublic = {
.name = "nport",
.description = "native n-port rational-model device",
.terms = &NPORTnTerms,
.numNames = &NPORTnTerms,
.termNames = NPORTnames,
.numInstanceParms = &NPORTpTSize,
.instanceParms = NPORTpTable,
.numModelParms = &NPORTmPTSize,
.modelParms = NPORTmPTable,
.flags = 0,
#ifdef XSPICE
.cm_func = NULL,
.num_conn = 0,
.conn = NULL,
.num_param = 0,
.param = NULL,
.num_inst_var = 0,
.inst_var = NULL,
#endif
},
.DEVparam = NPORTparam,
.DEVmodParam = NPORTmParam,
.DEVload = NPORTload,
.DEVsetup = NPORTsetup,
.DEVunsetup = NPORTunsetup,
.DEVpzSetup = NPORTsetup,
.DEVtemperature = NPORTtemp,
.DEVtrunc = NULL,
.DEVfindBranch = NULL,
.DEVacLoad = NPORTacLoad,
.DEVaccept = NULL,
.DEVdestroy = NULL,
.DEVmodDelete = NULL,
.DEVdelete = NPORTdelete,
.DEVsetic = NULL,
.DEVask = NPORTask,
.DEVmodAsk = NPORTmAsk,
.DEVpzLoad = NULL,
.DEVconvTest = NULL,
.DEVsenSetup = NULL,
.DEVsenLoad = NULL,
.DEVsenUpdate = NULL,
.DEVsenAcLoad = NULL,
.DEVsenPrint = NULL,
.DEVsenTrunc = NULL,
.DEVdisto = NULL,
.DEVnoise = NULL,
.DEVsoaCheck = NULL,
.DEVinstSize = &NPORTiSize,
.DEVmodSize = &NPORTmSize,
.DEVbindCSC = NPORTbindCSC,
.DEVbindCSCComplex = NPORTbindCSCComplex,
.DEVbindCSCComplexToReal = NPORTbindCSCComplexToReal,
};
SPICEdev *
get_nport_info(void)
{
static int built = 0;
if (!built) {
int i;
for (i = 0; i < NPORT_MAXTERMS; i++) {
char b[16];
snprintf(b, sizeof b, "%d", i + 1);
NPORTnames[i] = strdup(b);
}
built = 1;
}
return &NPORTinfo;
}

View File

@ -0,0 +1,12 @@
/* Enhancement-242: native n-port device -- init externs */
#ifndef _NPORTINIT_H
#define _NPORTINIT_H
extern IFparm NPORTpTable[];
extern IFparm NPORTmPTable[];
extern int NPORTpTSize;
extern int NPORTmPTSize;
extern int NPORTiSize;
extern int NPORTmSize;
#endif

View File

@ -0,0 +1,7 @@
/* Enhancement-242: native n-port device -- SPICEdev getter */
#ifndef DEV_NPORT
#define DEV_NPORT
SPICEdev *get_nport_info(void);
#endif

View File

@ -0,0 +1,213 @@
/**********
Enhancement-242: native n-port device -- DC + transient load, shared helper.
Y_ij(s) = d_ij + s*e_ij + sum_k res_ijk / (s - p_k) (all complex)
Port current leaving node i is I_i = sum_j Y_ij(s) * (V_j - V_ref).
* DC / .op / .dc : stamp the static conductance Y(0) directly.
* AC : nportacload.c stamps the complex Y(jw).
* transient : trapezoidal companion --
- d_ij -> constant conductance
- e_ij * s -> capacitor I = e dV/dt (trap companion)
- res/(s - p) -> first-order state dx/dt = p x + u, I += res x
(trap companion; x complex, conj pairs cancel to real)
The pole states x_jk depend only on the input j and pole k (shared across outputs
i), so they are updated exactly once per load (Phase A) and parked in CKTstate0;
Phase B recovers the history B_jk = x_jk - a_k*u_j from that parked value, so no
per-instance scratch is needed.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
/* Y_ij(s) for s = sre + j*sim. Shared with nportacload.c (DC and AC). */
void
NPORTadmittance(NPORTmodel *m, int i, int j, double sre, double sim,
double *yre, double *yim)
{
int N = m->NPORTnPorts, Np = m->NPORTnPoles, idx = i * N + j, k;
double yr = m->NPORTd[idx] + sre * m->NPORTe[idx];
double yi = sim * m->NPORTe[idx];
for (k = 0; k < Np; k++) {
double dre = sre - m->NPORTpoleRe[k];
double dim = sim - m->NPORTpoleIm[k];
double den = dre * dre + dim * dim;
double rr = m->NPORTresRe[idx * Np + k];
double ri = m->NPORTresIm[idx * Np + k];
if (den == 0.0) continue; /* s exactly on a pole */
yr += (rr * dre + ri * dim) / den;
yi += (ri * dre - rr * dim) / den;
}
*yre = yr;
*yim = yi;
}
/* trap coefficient a_k = (h/2) / (1 - (h/2) p_k) (complex). Depends on k, h. */
static void
nport_trap_a(double hh, double pr, double pi, double *ar, double *ai)
{
double dr = 1.0 - hh * pr; /* alpha = 1 - (h/2) p */
double di = - hh * pi;
double mag = dr * dr + di * di;
*ar = hh * dr / mag; /* (h/2) / alpha */
*ai = -hh * di / mag;
}
int
NPORTload(GENmodel *inModel, CKTcircuit *ckt)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
int i, j, k, N, Np;
for (; model; model = NPORTnextModel(model)) {
N = model->NPORTnPorts;
Np = model->NPORTnPoles;
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
/* ---- DC / operating point : static admittance Y(0) ---- */
if (ckt->CKTmode & MODEDC) {
for (i = 0; i < N; i++)
for (j = 0; j < N; j++) {
double yr, yi;
NPORTadmittance(model, i, j, 0.0, 0.0, &yr, &yi);
*(here->NPORTyPtr[i * N + j]) += yr;
*(here->NPORTyColPtr[i]) += -yr;
*(here->NPORTyRowPtr[j]) += -yr;
*(here->NPORTyRefPtr) += yr;
}
continue;
}
/* ---- transient : trapezoidal companion ---- */
{
double h = ckt->CKTdelta;
double hh = 0.5 * h;
int *node = GENnode(&here->gen);
int ref = here->NPORTrefNode;
int pBase = here->NPORTstateBase; /* poles: 4 per (j,k) */
int eBase = pBase + 4 * N * Np; /* e-cap: 2 per (i,j) */
int initTr = (ckt->CKTmode & MODEINITTRAN);
int uic = (ckt->CKTmode & MODEUIC);
double *st0 = ckt->CKTstate0;
double *st1 = ckt->CKTstate1;
double *rhsOld = ckt->CKTrhsOld;
/* ---- Phase A: advance the shared pole states x_jk ---- */
for (j = 0; j < N; j++) {
double uj = rhsOld[node[j]] - rhsOld[ref];
for (k = 0; k < Np; k++) {
double pr = model->NPORTpoleRe[k];
double pi = model->NPORTpoleIm[k];
int s = pBase + 4 * (j * Np + k);
double ar, ai, xr, xi, dxr, dxi, br, bi, mag;
nport_trap_a(hh, pr, pi, &ar, &ai);
if (initTr) {
if (uic) { /* zero initial state */
xr = xi = 0.0;
} else { /* DC steady state x = -u/p */
mag = pr * pr + pi * pi;
if (mag == 0.0) { xr = xi = 0.0; }
else { xr = -uj * pr / mag; xi = uj * pi / mag; }
}
dxr = dxi = 0.0; /* steady: dx/dt = 0 */
} else {
xr = st1[s + 0]; xi = st1[s + 1];
dxr = st1[s + 2]; dxi = st1[s + 3];
}
/* B = (x_n + (h/2) dx_n) / alpha, alpha = 1 - (h/2) p */
{
double nr = xr + hh * dxr;
double ni = xi + hh * dxi;
double dr = 1.0 - hh * pr;
double di = - hh * pi;
double dm = dr * dr + di * di;
br = (nr * dr + ni * di) / dm;
bi = (ni * dr - nr * di) / dm;
}
/* x_{n+1} = a*u_j + B (u_j real) */
{
double xnr = ar * uj + br;
double xni = ai * uj + bi;
/* dx_{n+1} = p*x_{n+1} + u_j */
double dnr = pr * xnr - pi * xni + uj;
double dni = pr * xni + pi * xnr;
st0[s + 0] = xnr; st0[s + 1] = xni;
st0[s + 2] = dnr; st0[s + 3] = dni;
}
}
}
/* ---- Phase B: stamp conductance + history for every (i,j) ---- */
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
int idx = i * N + j;
double uj = rhsOld[node[j]] - rhsOld[ref];
double geq = model->NPORTd[idx]; /* constant term */
double hist = 0.0;
/* pole contributions: recover B_jk = x_{n+1} - a_k*u_j */
for (k = 0; k < Np; k++) {
double pr = model->NPORTpoleRe[k];
double pi = model->NPORTpoleIm[k];
int s = pBase + 4 * (j * Np + k);
double ar, ai, br, bi, rr, ri;
nport_trap_a(hh, pr, pi, &ar, &ai);
br = st0[s + 0] - ar * uj;
bi = st0[s + 1] - ai * uj;
rr = model->NPORTresRe[idx * Np + k];
ri = model->NPORTresIm[idx * Np + k];
/* Re[res * a] adds to conductance; Re[res * B] to hist */
geq += rr * ar - ri * ai;
hist += rr * br - ri * bi;
}
/* e-term capacitor: I = e dU/dt (trapezoidal) */
{
double e = model->NPORTe[idx];
if (e != 0.0) {
int es = eBase + 2 * idx;
double uPrev, iPrev, geqE, iNew, ieq;
if (initTr) {
uPrev = uic ? 0.0 : uj;
iPrev = 0.0;
} else {
uPrev = st1[es + 0];
iPrev = st1[es + 1];
}
geqE = 2.0 * e / h;
iNew = geqE * (uj - uPrev) - iPrev;
ieq = -(geqE * uPrev + iPrev);
st0[es + 0] = uj;
st0[es + 1] = iNew;
geq += geqE;
hist += ieq;
}
}
/* stamp four-corner conductance */
*(here->NPORTyPtr[idx]) += geq;
*(here->NPORTyColPtr[i]) += -geq;
*(here->NPORTyRowPtr[j]) += -geq;
*(here->NPORTyRefPtr) += geq;
/* stamp equivalent-current history (ceq convention) */
ckt->CKTrhs[node[i]] -= hist;
ckt->CKTrhs[ref] += hist;
}
}
}
}
}
return OK;
}

View File

@ -0,0 +1,30 @@
/**********
Enhancement-242: native n-port device -- model query.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "ngspice/ifsim.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
int
NPORTmAsk(CKTcircuit *ckt, GENmodel *inModel, int which, IFvalue *value)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NG_IGNORE(ckt);
switch (which) {
case NPORT_MOD_FILE:
value->sValue = model->NPORTfile;
return OK;
case NPORT_NPORTS:
value->iValue = model->NPORTnPorts;
return OK;
case NPORT_NPOLES:
value->iValue = model->NPORTnPoles;
return OK;
default:
return E_BADPARM;
}
}

View File

@ -0,0 +1,28 @@
/**********
Enhancement-242: native n-port device -- model parameter parsing.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/const.h"
#include "ngspice/ifsim.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
int
NPORTmParam(int param, IFvalue *value, GENmodel *inModel)
{
NPORTmodel *model = (NPORTmodel *)inModel;
switch (param) {
case NPORT_MOD_NPORT:
/* the bare `nport` type keyword -- nothing to store */
break;
case NPORT_MOD_FILE:
model->NPORTfile = strdup(value->sValue);
model->NPORTfileGiven = TRUE;
break;
default:
return E_BADPARM;
}
return OK;
}

View File

@ -0,0 +1,23 @@
/**********
Enhancement-242: native n-port device -- instance parameter parsing.
The n-port has no instance parameters (all data comes from the .model fit file),
so this only exists to satisfy the PARSECALL path for `N` instances.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/ifsim.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
int
NPORTparam(int param, IFvalue *value, GENinstance *inst, IFvalue *select)
{
NG_IGNORE(value);
NG_IGNORE(inst);
NG_IGNORE(select);
switch (param) {
default:
return E_BADPARM;
}
}

View File

@ -0,0 +1,159 @@
/**********
Enhancement-242: native n-port device -- `.nport` fit-file reader.
The `.nport` file is a compact text description of a vector-fitted Y-parameter
model, written by `pre_snp -native` and read by the native n-port device at
temperature time. Kept as an independently testable unit (compile with
-DNPORT_TEST for a standalone reader/dumper) so the parser is validated apart
from the ngspice device framework.
Format (whitespace/newline separated, '#' to end-of-line is a comment):
NPORT 1 # format tag + version
nports N
npoles Np # real-canonical layout: real poles first, then
# each complex pole as an adjacent conjugate pair
poles # Np lines, "re im"
re0 im0
...
d # N*N values, row-major Y-index (i*N+j), constant term
...
e # N*N values, s-linear term
...
res # N*N*Np "re im" pairs, index ((i*N+j)*Np + k)
re im
...
Sections may appear in any order after the header; each is introduced by its
keyword. All numeric fields are plain doubles.
**********/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* token reader: skips '#'-to-EOL comments; returns 1 on a token, 0 on EOF */
static int
nport_tok(FILE *f, char *buf, int cap)
{
int c, n = 0;
for (;;) {
c = fgetc(f);
if (c == EOF) { buf[n] = '\0'; return n > 0; }
if (c == '#') { while (c != '\n' && c != EOF) c = fgetc(f); if (n) break; else continue; }
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
if (n > 0) break; /* end of a token */
continue; /* leading whitespace */
}
if (n < cap - 1) buf[n++] = (char) c;
}
buf[n] = '\0';
return 1;
}
static int
nport_int(FILE *f, int *dst)
{
char b[64];
if (!nport_tok(f, b, sizeof b)) return 0;
*dst = atoi(b);
return 1;
}
static int
nport_dbl(FILE *f, double *dst)
{
char b[64];
if (!nport_tok(f, b, sizeof b)) return 0;
*dst = atof(b);
return 1;
}
/* Pure-C core: fills freshly malloc'd arrays. Returns 0 on success, non-zero on
* error (message in `err`). Caller frees the arrays. */
int
snp_nport_read(const char *path,
int *pN, int *pNp,
double **ppoleRe, double **ppoleIm,
double **pd, double **pe,
double **presRe, double **presIm,
char *err, int errlen)
{
FILE *f = fopen(path, "r");
int N = 0, Np = -1, ver = 0, i, got_hdr = 0;
double *poleRe = NULL, *poleIm = NULL, *d = NULL, *e = NULL;
double *resRe = NULL, *resIm = NULL;
char tok[256];
*ppoleRe = *ppoleIm = *pd = *pe = *presRe = *presIm = NULL;
*pN = *pNp = 0;
if (!f) { snprintf(err, errlen, "nport: cannot open fit file '%s'", path); return 1; }
#define FAIL(msg) do { snprintf(err, errlen, "nport: %s in '%s'", msg, path); goto fail; } while (0)
#define RDI(dst) do { if (!nport_int(f, (dst))) FAIL("expected an integer"); } while (0)
#define RDD(dst) do { if (!nport_dbl(f, (dst))) FAIL("expected a number"); } while (0)
/* header: NPORT/nports/npoles in any order until the first section keyword */
while (nport_tok(f, tok, sizeof tok)) {
if (!strcmp(tok, "NPORT")) { RDI(&ver); got_hdr = 1; }
else if (!strcmp(tok, "nports")) { RDI(&N); }
else if (!strcmp(tok, "npoles")) { RDI(&Np); }
else if (!strcmp(tok, "poles") || !strcmp(tok, "d") ||
!strcmp(tok, "e") || !strcmp(tok, "res")) break;
else FAIL("unexpected header token");
}
if (!got_hdr || N <= 0 || Np < 0) FAIL("missing/invalid NPORT/nports/npoles header");
if (ver != 1) FAIL("unsupported .nport format version");
poleRe = calloc((size_t) (Np > 0 ? Np : 1), sizeof(double));
poleIm = calloc((size_t) (Np > 0 ? Np : 1), sizeof(double));
d = calloc((size_t) N * N, sizeof(double));
e = calloc((size_t) N * N, sizeof(double));
resRe = calloc((size_t) N * N * (Np > 0 ? Np : 1), sizeof(double));
resIm = calloc((size_t) N * N * (Np > 0 ? Np : 1), sizeof(double));
if (!poleRe || !poleIm || !d || !e || !resRe || !resIm) FAIL("out of memory");
/* `tok` holds the first section keyword; process each section in turn */
do {
if (!strcmp(tok, "poles")) { for (i = 0; i < Np; i++) { RDD(&poleRe[i]); RDD(&poleIm[i]); } }
else if (!strcmp(tok, "d")) { for (i = 0; i < N * N; i++) RDD(&d[i]); }
else if (!strcmp(tok, "e")) { for (i = 0; i < N * N; i++) RDD(&e[i]); }
else if (!strcmp(tok, "res")) { for (i = 0; i < N*N*Np; i++) { RDD(&resRe[i]); RDD(&resIm[i]); } }
else FAIL("unexpected section keyword");
} while (nport_tok(f, tok, sizeof tok));
fclose(f);
*pN = N; *pNp = Np;
*ppoleRe = poleRe; *ppoleIm = poleIm;
*pd = d; *pe = e; *presRe = resRe; *presIm = resIm;
return 0;
fail:
fclose(f);
free(poleRe); free(poleIm); free(d); free(e); free(resRe); free(resIm);
return 1;
#undef FAIL
#undef RDI
#undef RDD
}
#ifdef NPORT_TEST
int main(int argc, char **argv)
{
int N, Np, i;
double *pr, *pi, *d, *e, *rr, *ri;
char err[256];
if (argc < 2) { fprintf(stderr, "usage: %s file.nport\n", argv[0]); return 2; }
if (snp_nport_read(argv[1], &N, &Np, &pr, &pi, &d, &e, &rr, &ri, err, sizeof err)) {
fprintf(stderr, "%s\n", err); return 1;
}
printf("nports=%d npoles=%d\n", N, Np);
for (i = 0; i < Np; i++) printf(" pole[%d] = %g %+gj\n", i, pr[i], pi[i]);
for (i = 0; i < N * N; i++) printf(" d[%d]=%g e[%d]=%g\n", i, d[i], i, e[i]);
for (i = 0; i < N*N*Np; i++) printf(" res[%d] = %g %+gj\n", i, rr[i], ri[i]);
free(pr); free(pi); free(d); free(e); free(rr); free(ri);
return 0;
}
#endif

View File

@ -0,0 +1,125 @@
/**********
Enhancement-242: native n-port device -- setup.
Loads the model's `.nport` fit file (once), then for each instance allocates the
direct admittance-stamp matrix elements (a multi-terminal conductance; no branch
currents / extra unknowns -- so it scales cleanly to many ports).
**********/
#include "ngspice/ngspice.h"
#include "ngspice/smpdefs.h"
#include "ngspice/cktdefs.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
#include "ngspice/suffix.h"
/* pure-C reader in nportread.c */
extern int snp_nport_read(const char *path, int *pN, int *pNp,
double **ppoleRe, double **ppoleIm,
double **pd, double **pe,
double **presRe, double **presIm,
char *err, int errlen);
/* fill a model from its .nport file */
int
NPORTreadFile(NPORTmodel *model)
{
char err[256];
if (model->NPORTloaded)
return OK;
if (!model->NPORTfileGiven || !model->NPORTfile) {
fprintf(stderr, "nport: model '%s' has no file= parameter\n",
model->NPORTmodName);
return E_BADPARM;
}
if (snp_nport_read(model->NPORTfile,
&model->NPORTnPorts, &model->NPORTnPoles,
&model->NPORTpoleRe, &model->NPORTpoleIm,
&model->NPORTd, &model->NPORTe,
&model->NPORTresRe, &model->NPORTresIm,
err, sizeof err)) {
fprintf(stderr, "%s\n", err);
return E_BADPARM;
}
model->NPORTloaded = 1;
return OK;
}
int
NPORTsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt, int *states)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
int error, i, j, N, Np, ref;
int *node;
NG_IGNORE(ckt);
for (; model; model = NPORTnextModel(model)) {
if ((error = NPORTreadFile(model)) != OK)
return error;
N = model->NPORTnPorts;
Np = model->NPORTnPoles;
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
here->NPORTn = N;
node = GENnode(&here->gen); /* ports 0..N-1, ref at [N] */
ref = node[N];
here->NPORTrefNode = ref;
/* transient companion state:
* poles: 4 per (input j, pole k) [x_re, x_im, dx_re, dx_im]
* e-term: 2 per (i,j) [charge, current] for NIintegrate */
here->NPORTstateBase = *states;
*states += 4 * N * Np + 2 * N * N;
here->NPORTyPtr = TMALLOC(double *, N * N);
here->NPORTyColPtr = TMALLOC(double *, N);
here->NPORTyRowPtr = TMALLOC(double *, N);
here->NPORTyBind = TMALLOC(BindElement *, N * N); /* KLU (NULL until bound) */
here->NPORTyColBind = TMALLOC(BindElement *, N);
here->NPORTyRowBind = TMALLOC(BindElement *, N);
here->NPORTyRefBind = NULL;
here->NPORTallocated = 1;
#define TST(dst, r, c) do { \
if ((dst = SMPmakeElt(matrix, (r), (c))) == NULL) return E_NOMEM; } while (0)
for (i = 0; i < N; i++) {
TST(here->NPORTyColPtr[i], node[i], ref); /* (node_i, ref) */
TST(here->NPORTyRowPtr[i], ref, node[i]); /* (ref, node_i) */
for (j = 0; j < N; j++)
TST(here->NPORTyPtr[i * N + j], node[i], node[j]);
}
TST(here->NPORTyRefPtr, ref, ref); /* (ref, ref) */
#undef TST
}
}
return OK;
}
int
NPORTunsetup(GENmodel *inModel, CKTcircuit *ckt)
{
NPORTmodel *model = (NPORTmodel *)inModel;
NPORTinstance *here;
NG_IGNORE(ckt);
for (; model; model = NPORTnextModel(model)) {
for (here = NPORTinstances(model); here; here = NPORTnextInstance(here)) {
if (here->NPORTallocated) {
tfree(here->NPORTyPtr);
tfree(here->NPORTyColPtr);
tfree(here->NPORTyRowPtr);
tfree(here->NPORTyBind);
tfree(here->NPORTyColBind);
tfree(here->NPORTyRowBind);
here->NPORTallocated = 0;
}
}
}
return OK;
}

View File

@ -0,0 +1,18 @@
/**********
Enhancement-242: native n-port device -- temperature.
All fit data is frequency/temperature-independent and loaded in NPORTsetup, so
this is a no-op. (Kept as a hook for future temperature-scaled fits.)
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "nportdefs.h"
#include "ngspice/sperror.h"
int
NPORTtemp(GENmodel *inModel, CKTcircuit *ckt)
{
NG_IGNORE(inModel);
NG_IGNORE(ckt);
return OK;
}

View File

@ -90,8 +90,10 @@ void INP2N(CKTcircuit *ckt, INPtables *tab, struct card *current) {
mdfast = thismodel->INPmodfast;
dev = ft_sim->devices[type];
if (!dev->registry_entry) {
LITERR("incorrect model type! Expected OSDI device");
/* E-242: the N dispatcher hosts OSDI devices (registry_entry set) and the
* native n-port rational device (name "nport"); accept either. */
if (!dev->registry_entry && strcmp(dev->name, "nport") != 0) {
LITERR("incorrect model type! Expected OSDI or nport device");
return;
}

View File

@ -1338,6 +1338,10 @@
<ClInclude Include="..\src\spicelib\devices\nbjt\nbjtext.h" />
<ClInclude Include="..\src\spicelib\devices\nbjt\nbjtinit.h" />
<ClInclude Include="..\src\spicelib\devices\nbjt\nbjtitf.h" />
<ClInclude Include="..\src\spicelib\devices\nport\nportdefs.h" />
<ClInclude Include="..\src\spicelib\devices\nport\nportext.h" />
<ClInclude Include="..\src\spicelib\devices\nport\nportinit.h" />
<ClInclude Include="..\src\spicelib\devices\nport\nportitf.h" />
<ClInclude Include="..\src\spicelib\devices\numd2\numd2def.h" />
<ClInclude Include="..\src\spicelib\devices\numd2\numd2ext.h" />
<ClInclude Include="..\src\spicelib\devices\numd2\numd2init.h" />
@ -1412,6 +1416,7 @@
<ClInclude Include="..\src\include\cppduals\duals\dual">
<FileType>Document</FileType>
</ClInclude>
<None Include="..\src\spicelib\devices\nport\Makefile.am" />
<None Include="..\src\xspice\icm\objects.inc" />
</ItemGroup>
<ItemGroup>
@ -2608,6 +2613,19 @@
<ClCompile Include="..\src\spicelib\devices\nbjt\nbjtset.c" />
<ClCompile Include="..\src\spicelib\devices\nbjt\nbjttemp.c" />
<ClCompile Include="..\src\spicelib\devices\nbjt\nbjttrun.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nport.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportacload.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportask.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportbindCSC.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportdel.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportinit.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportload.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportmask.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportmpar.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportparam.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportread.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nportsetup.c" />
<ClCompile Include="..\src\spicelib\devices\nport\nporttemp.c" />
<ClCompile Include="..\src\spicelib\devices\numd2\nud2.c" />
<ClCompile Include="..\src\spicelib\devices\numd2\nud2acld.c" />
<ClCompile Include="..\src\spicelib\devices\numd2\nud2ask.c" />