pa-242
This commit is contained in:
parent
f99b02c957
commit
01c1a472c5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/* Enhancement-242: native n-port device -- SPICEdev getter */
|
||||
#ifndef DEV_NPORT
|
||||
#define DEV_NPORT
|
||||
|
||||
SPICEdev *get_nport_info(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue