From 01c1a472c5e1f392f12d8637e31839c505eeb2b9 Mon Sep 17 00:00:00 2001 From: Meisam Bahadori Date: Thu, 30 Jul 2026 13:52:01 +0200 Subject: [PATCH] pa-242 --- configure.ac | 1 + src/Makefile.am | 1 + src/frontend/com_presnp.c | 152 +++++++++++++++ src/frontend/snp2va.c | 70 ++++++- src/frontend/snp2va.cpp | 88 ++++++++- src/frontend/snp2va.h | 4 + src/spicelib/devices/Makefile.am | 2 + src/spicelib/devices/dev.c | 2 + src/spicelib/devices/nport/Makefile.am | 28 +++ src/spicelib/devices/nport/nport.c | 26 +++ src/spicelib/devices/nport/nportacload.c | 45 +++++ src/spicelib/devices/nport/nportask.c | 26 +++ src/spicelib/devices/nport/nportbindCSC.c | 126 +++++++++++++ src/spicelib/devices/nport/nportdefs.h | 123 +++++++++++++ src/spicelib/devices/nport/nportdel.c | 25 +++ src/spicelib/devices/nport/nportext.h | 19 ++ src/spicelib/devices/nport/nportinit.c | 95 ++++++++++ src/spicelib/devices/nport/nportinit.h | 12 ++ src/spicelib/devices/nport/nportitf.h | 7 + src/spicelib/devices/nport/nportload.c | 213 ++++++++++++++++++++++ src/spicelib/devices/nport/nportmask.c | 30 +++ src/spicelib/devices/nport/nportmpar.c | 28 +++ src/spicelib/devices/nport/nportparam.c | 23 +++ src/spicelib/devices/nport/nportread.c | 159 ++++++++++++++++ src/spicelib/devices/nport/nportsetup.c | 125 +++++++++++++ src/spicelib/devices/nport/nporttemp.c | 18 ++ src/spicelib/parser/inp2n.c | 6 +- visualc/vngspice.vcxproj | 18 ++ 28 files changed, 1458 insertions(+), 14 deletions(-) create mode 100644 src/frontend/com_presnp.c create mode 100644 src/spicelib/devices/nport/Makefile.am create mode 100644 src/spicelib/devices/nport/nport.c create mode 100644 src/spicelib/devices/nport/nportacload.c create mode 100644 src/spicelib/devices/nport/nportask.c create mode 100644 src/spicelib/devices/nport/nportbindCSC.c create mode 100644 src/spicelib/devices/nport/nportdefs.h create mode 100644 src/spicelib/devices/nport/nportdel.c create mode 100644 src/spicelib/devices/nport/nportext.h create mode 100644 src/spicelib/devices/nport/nportinit.c create mode 100644 src/spicelib/devices/nport/nportinit.h create mode 100644 src/spicelib/devices/nport/nportitf.h create mode 100644 src/spicelib/devices/nport/nportload.c create mode 100644 src/spicelib/devices/nport/nportmask.c create mode 100644 src/spicelib/devices/nport/nportmpar.c create mode 100644 src/spicelib/devices/nport/nportparam.c create mode 100644 src/spicelib/devices/nport/nportread.c create mode 100644 src/spicelib/devices/nport/nportsetup.c create mode 100644 src/spicelib/devices/nport/nporttemp.c diff --git a/configure.ac b/configure.ac index c11bb72f1..6dec9c692 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 76dad79db..eac3e39ae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/frontend/com_presnp.c b/src/frontend/com_presnp.c new file mode 100644 index 000000000..f820328f9 --- /dev/null +++ b/src/frontend/com_presnp.c @@ -0,0 +1,152 @@ +/* Enhancement-200: the `pre_snp` control command. + * + * `pre_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 .osdi next to the source -- + * so `pre_osdi .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 + +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] [module]\n" + " -osdi (default) Touchstone -> Verilog-A -> openvaf-r -> .osdi,\n" + " then load with `pre_osdi .osdi`.\n" + " -native Touchstone -> .nport for the built-in n-port\n" + " device (no compiler); use it in the deck with\n" + " `N1 m` / `.model m nport(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 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); +} diff --git a/src/frontend/snp2va.c b/src/frontend/snp2va.c index aa1c8273b..1c8aa120c 100644 --- a/src/frontend/snp2va.c +++ b/src/frontend/snp2va.c @@ -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; } diff --git a/src/frontend/snp2va.cpp b/src/frontend/snp2va.cpp index ad4a5bdd8..1c8aa120c 100644 --- a/src/frontend/snp2va.cpp +++ b/src/frontend/snp2va.cpp @@ -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=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; } diff --git a/src/frontend/snp2va.h b/src/frontend/snp2va.h index 010d58bba..5812918ca 100644 --- a/src/frontend/snp2va.h +++ b/src/frontend/snp2va.h @@ -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 diff --git a/src/spicelib/devices/Makefile.am b/src/spicelib/devices/Makefile.am index e0d678cf1..711f48f02 100644 --- a/src/spicelib/devices/Makefile.am +++ b/src/spicelib/devices/Makefile.am @@ -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 \ diff --git a/src/spicelib/devices/dev.c b/src/spicelib/devices/dev.c index b3514919e..d74e778d8 100644 --- a/src/spicelib/devices/dev.c +++ b/src/spicelib/devices/dev.c @@ -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, diff --git a/src/spicelib/devices/nport/Makefile.am b/src/spicelib/devices/nport/Makefile.am new file mode 100644 index 000000000..2ea8a3b6b --- /dev/null +++ b/src/spicelib/devices/nport/Makefile.am @@ -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 diff --git a/src/spicelib/devices/nport/nport.c b/src/spicelib/devices/nport/nport.c new file mode 100644 index 000000000..7661d7841 --- /dev/null +++ b/src/spicelib/devices/nport/nport.c @@ -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); diff --git a/src/spicelib/devices/nport/nportacload.c b/src/spicelib/devices/nport/nportacload.c new file mode 100644 index 000000000..6d3fb77be --- /dev/null +++ b/src/spicelib/devices/nport/nportacload.c @@ -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; +} diff --git a/src/spicelib/devices/nport/nportask.c b/src/spicelib/devices/nport/nportask.c new file mode 100644 index 000000000..fffe677ac --- /dev/null +++ b/src/spicelib/devices/nport/nportask.c @@ -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; + } +} diff --git a/src/spicelib/devices/nport/nportbindCSC.c b/src/spicelib/devices/nport/nportbindCSC.c new file mode 100644 index 000000000..5c679e3f9 --- /dev/null +++ b/src/spicelib/devices/nport/nportbindCSC.c @@ -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; +} diff --git a/src/spicelib/devices/nport/nportdefs.h b/src/spicelib/devices/nport/nportdefs.h new file mode 100644 index 000000000..37d5462b0 --- /dev/null +++ b/src/spicelib/devices/nport/nportdefs.h @@ -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 */ diff --git a/src/spicelib/devices/nport/nportdel.c b/src/spicelib/devices/nport/nportdel.c new file mode 100644 index 000000000..3dc1e836e --- /dev/null +++ b/src/spicelib/devices/nport/nportdel.c @@ -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; +} diff --git a/src/spicelib/devices/nport/nportext.h b/src/spicelib/devices/nport/nportext.h new file mode 100644 index 000000000..75d11ef8c --- /dev/null +++ b/src/spicelib/devices/nport/nportext.h @@ -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 diff --git a/src/spicelib/devices/nport/nportinit.c b/src/spicelib/devices/nport/nportinit.c new file mode 100644 index 000000000..50271b45a --- /dev/null +++ b/src/spicelib/devices/nport/nportinit.c @@ -0,0 +1,95 @@ +/********** +Enhancement-242: native n-port device -- SPICEdev descriptor / init. +**********/ + +#include "ngspice/config.h" +#include +#include +#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; +} diff --git a/src/spicelib/devices/nport/nportinit.h b/src/spicelib/devices/nport/nportinit.h new file mode 100644 index 000000000..38dcd64fd --- /dev/null +++ b/src/spicelib/devices/nport/nportinit.h @@ -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 diff --git a/src/spicelib/devices/nport/nportitf.h b/src/spicelib/devices/nport/nportitf.h new file mode 100644 index 000000000..c9ec883b2 --- /dev/null +++ b/src/spicelib/devices/nport/nportitf.h @@ -0,0 +1,7 @@ +/* Enhancement-242: native n-port device -- SPICEdev getter */ +#ifndef DEV_NPORT +#define DEV_NPORT + +SPICEdev *get_nport_info(void); + +#endif diff --git a/src/spicelib/devices/nport/nportload.c b/src/spicelib/devices/nport/nportload.c new file mode 100644 index 000000000..bb0135cfb --- /dev/null +++ b/src/spicelib/devices/nport/nportload.c @@ -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; +} diff --git a/src/spicelib/devices/nport/nportmask.c b/src/spicelib/devices/nport/nportmask.c new file mode 100644 index 000000000..6ad0454d1 --- /dev/null +++ b/src/spicelib/devices/nport/nportmask.c @@ -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; + } +} diff --git a/src/spicelib/devices/nport/nportmpar.c b/src/spicelib/devices/nport/nportmpar.c new file mode 100644 index 000000000..78075edd0 --- /dev/null +++ b/src/spicelib/devices/nport/nportmpar.c @@ -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; +} diff --git a/src/spicelib/devices/nport/nportparam.c b/src/spicelib/devices/nport/nportparam.c new file mode 100644 index 000000000..dcd9c1496 --- /dev/null +++ b/src/spicelib/devices/nport/nportparam.c @@ -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; + } +} diff --git a/src/spicelib/devices/nport/nportread.c b/src/spicelib/devices/nport/nportread.c new file mode 100644 index 000000000..348a3b87c --- /dev/null +++ b/src/spicelib/devices/nport/nportread.c @@ -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 +#include +#include + +/* 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 diff --git a/src/spicelib/devices/nport/nportsetup.c b/src/spicelib/devices/nport/nportsetup.c new file mode 100644 index 000000000..8bdb141fa --- /dev/null +++ b/src/spicelib/devices/nport/nportsetup.c @@ -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; +} diff --git a/src/spicelib/devices/nport/nporttemp.c b/src/spicelib/devices/nport/nporttemp.c new file mode 100644 index 000000000..d6f8ec620 --- /dev/null +++ b/src/spicelib/devices/nport/nporttemp.c @@ -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; +} diff --git a/src/spicelib/parser/inp2n.c b/src/spicelib/parser/inp2n.c index 2593c7465..c264f0c6a 100644 --- a/src/spicelib/parser/inp2n.c +++ b/src/spicelib/parser/inp2n.c @@ -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; } diff --git a/visualc/vngspice.vcxproj b/visualc/vngspice.vcxproj index ecb7bcf7f..8c811bcc9 100644 --- a/visualc/vngspice.vcxproj +++ b/visualc/vngspice.vcxproj @@ -1338,6 +1338,10 @@ + + + + @@ -1412,6 +1416,7 @@ Document + @@ -2608,6 +2613,19 @@ + + + + + + + + + + + + +