This commit is contained in:
Meisam Bahadori 2026-07-22 14:55:19 +02:00 committed by Holger Vogt
parent b54bf483fb
commit 262781618a
5 changed files with 1437 additions and 1299 deletions

View File

@ -225,6 +225,10 @@ struct comm spcp_coms[] = {
{ 1, 041000, 041000, 041000 }, E_DEFHMASK, 0, LOTS,
NULL,
"file : Send s-param data to file." } ,
{ "wrsnp", com_write_sparam, FALSE, TRUE,
{ 1, 041000, 041000, 041000 }, E_DEFHMASK, 0, LOTS,
NULL,
"file : Send N-port s-param data to a Touchstone file." } ,
{ "hardcopy", com_hardcopy, FALSE, TRUE,
{ 1, 041000, 041000, 041000 }, E_DEFHMASK, 0, LOTS,
NULL,
@ -776,6 +780,10 @@ struct comm nutcp_coms[] = {
{ 1, 041000, 041000, 041000 }, E_DEFHMASK, 0, LOTS,
NULL,
"file : Send s-param data to file." } ,
{ "wrsnp", com_write_sparam, FALSE, TRUE,
{ 1, 041000, 041000, 041000 }, E_DEFHMASK, 0, LOTS,
NULL,
"file : Send N-port s-param data to a Touchstone file." } ,
{ "hardcopy", com_hardcopy, FALSE, TRUE,
{ 1, 041000, 041000, 041000 }, E_DEFHMASK, 0, LOTS,
NULL,

View File

@ -744,13 +744,92 @@ done:
}
/* Enhancement-64: write an N-port Touchstone v1 file (.sNp) directly from
the S_i_j vectors of the current .sp plot. Layout per the Touchstone 1.x
spec: `# Hz S RI R <Rbase>` option line; for N >= 3 the matrix is
row-major with at most FOUR complex pairs per data line and every matrix
row starting on a new line (the first row follows the frequency value);
a 1-port is a single pair per line. (The classic 2-port S11 S21 S12 S22
column order is handled by the original spar_write() path.) */
static void
spar_write_np(const char *file, int nports, double Rbaseval)
{
struct dvec *freqv = vec_get("frequency");
struct dvec **sv;
FILE *fp;
int i, j, k, npts, prec, inrow;
if (!freqv) {
fprintf(stderr, "Error: no frequency vector (run a .sp analysis first)\n");
return;
}
npts = freqv->v_length;
prec = 6;
sv = TMALLOC(struct dvec *, (size_t) (nports * nports));
for (i = 0; i < nports; i++)
for (j = 0; j < nports; j++) {
char nb[40];
(void) sprintf(nb, "S_%d_%d", i + 1, j + 1);
sv[i * nports + j] = vec_get(nb);
if (!sv[i * nports + j] || sv[i * nports + j]->v_length != npts) {
fprintf(stderr, "Error: vector %s missing or of wrong length\n", nb);
tfree(sv);
return;
}
}
if ((fp = fopen(file, "w")) == NULL) {
perror(file);
tfree(sv);
return;
}
fprintf(fp, "!%d-port S-parameter file\n", nports);
fprintf(fp, "!Title: %s\n", freqv->v_plot ? freqv->v_plot->pl_title : "");
fprintf(fp, "!Generated by ngspice at %s\n",
freqv->v_plot ? freqv->v_plot->pl_date : "");
fprintf(fp, "# Hz S RI R %g\n", Rbaseval);
for (k = 0; k < npts; k++) {
double f = isreal(freqv) ? freqv->v_realdata[k]
: realpart(freqv->v_compdata[k]);
fprintf(fp, "% .*e", prec, f);
for (i = 0; i < nports; i++) {
if (i > 0)
fprintf(fp, "%*s", prec + 9, ""); /* align continuation rows */
inrow = 0;
for (j = 0; j < nports; j++) {
struct dvec *v = sv[i * nports + j];
double re = isreal(v) ? v->v_realdata[k]
: realpart(v->v_compdata[k]);
double im = isreal(v) ? 0.0
: imagpart(v->v_compdata[k]);
if (inrow == 4) { /* max 4 pairs per line */
fprintf(fp, "\n%*s", prec + 9, "");
inrow = 0;
}
fprintf(fp, " % .*e % .*e", prec, re, prec, im);
inrow++;
}
fprintf(fp, "\n"); /* each matrix row on its own line */
}
}
(void) fclose(fp);
fprintf(stdout, "%d-port S-parameters written to %s\n", nports, file);
tfree(sv);
}
/* Write scattering parameters into a file with Touchstone File Format Version 1
with command wrs2p file .
with command wrs2p file (2 ports) or wrsnp file (any port count).
Format info from http://www.eda.org/ibis/touchstone_ver2.0/touchstone_ver2_0.pdf
See example 13 on page 15: Two port, ASCII, real-imaginary
Check if S_1_1, S_2_1, S_1_2, S_2_2 and frequency vectors are available
Check if vector Rbase is available
Call spar_write()
Check if vector Rbase is available (the .sp analysis publishes it since
Enhancement-64, so no manual `let Rbase = ...` is needed anymore)
Call spar_write() (2-port) or spar_write_np() (N-port)
*/
void
@ -766,12 +845,42 @@ com_write_sparam(wordlist *wl)
struct plot *tpl, newplot;
double Rbaseval;
int nports = 0;
if (wl)
file = wl->wl_word;
else
file = "s_param.s2p";
fprintf(stderr, "Note: only 2 ports 1 and 2 are supported by wrs2p\n");
/* Enhancement-64: how many ports does the current sp plot hold? */
while (nports < 99) {
char nb[40];
(void) sprintf(nb, "S_%d_%d", nports + 1, nports + 1);
if (!vec_get(nb))
break;
nports++;
}
if (nports == 0) {
fprintf(stderr, "Error: no S-parameter vectors found (run a .sp analysis first)\n");
return;
}
/* Enhancement-64: the sp analysis publishes Rbase (the ports'
reference resistance); a user-defined `let Rbase = ...` still
overrides. The sp plot is complex, so read either data form. */
Rbasevec = vec_get("Rbase");
if (Rbasevec) {
Rbaseval = isreal(Rbasevec) ? Rbasevec->v_realdata[0]
: realpart(Rbasevec->v_compdata[0]);
} else {
fprintf(stderr, "Error: No Rbase vector given\n");
return;
}
if (nports != 2) {
spar_write_np(file, nports, Rbaseval);
return;
}
/* generate wordlist with all vectors required*/
sbuf[0] = "frequency";
@ -800,14 +909,6 @@ com_write_sparam(wordlist *wl)
;
}
Rbasevec = vec_get("Rbase");
if (Rbasevec) {
Rbaseval = Rbasevec->v_realdata[0];
} else {
fprintf(stderr, "Error: No Rbase vector given\n");
goto done;
}
/* Now we have to write them out plot by plot. */
while (vecs) {

File diff suppressed because it is too large Load Diff

View File

@ -128,7 +128,8 @@ CKTspDump(CKTcircuit *ckt, double freq, runDesc *plot, int doNoise)
rhsold = ckt->CKTrhsOld;
irhsold = ckt->CKTirhsOld;
freqData.rValue = freq;
int extraSPdataCount = 3* ckt->CKTportCount * ckt->CKTportCount;
/* Enhancement-64: +1 for the Rbase vector */
int extraSPdataCount = 3* ckt->CKTportCount * ckt->CKTportCount + 1;
valueData.v.numValue = ckt->CKTmaxEqNum - 1 + extraSPdataCount;
int datasize = ckt->CKTmaxEqNum - 1 + extraSPdataCount;
@ -189,6 +190,17 @@ CKTspDump(CKTcircuit *ckt, double freq, runDesc *plot, int doNoise)
}
}
/* Enhancement-64: reference resistance of the ports (matches the
"Rbase" UID registered after the Z block in span.c). Read z0
straight from port 1 -- the refPortY0 global is only set on the
noise path. */
{
VSRCinstance *refPort = (VSRCinstance *) (ckt->CKTrfPorts[0]);
data[nPlot].real = refPort->VSRCportZ0;
data[nPlot].imag = 0.0;
nPlot++;
}
if (doNoise)
{
// Put Cy data

View File

@ -390,11 +390,10 @@ SPan(CKTcircuit* ckt, int restart)
controlled_exit(EXIT_BAD);
}
if (ckt->CKTportCount == 1)
{
fprintf(stderr, "\nError: Only one RF Port is found, we need at least two!\n");
controlled_exit(EXIT_BAD);
}
/* Enhancement-64: a single port is a perfectly good S-parameter
measurement (reflection, .s1p); the matrix machinery below is
N-general, so the old hard error was over-strict. The 2-port-only
noise-parameter block stays gated on CKTportCount == 2. */
#ifdef XSPICE
/* Tell the code models what mode we're in */
@ -497,7 +496,9 @@ SPan(CKTcircuit* ckt, int restart)
plot = NULL;
}
int extraSPdataLength = 3 * ckt->CKTportCount * ckt->CKTportCount;
/* Enhancement-64: +1 for the Rbase vector (the ports' reference
resistance) so wrs2p/wrsnp work without a manual `let Rbase` */
int extraSPdataLength = 3 * ckt->CKTportCount * ckt->CKTportCount + 1;
if (job->SPdoNoise)
{
extraSPdataLength += ckt->CKTportCount * ckt->CKTportCount; // Add Cy
@ -538,6 +539,11 @@ SPan(CKTcircuit* ckt, int restart)
SPfrontEnd->IFnewUid(ckt, &(nameList[numNames++]), NULL, tmpBuf, UID_OTHER, NULL);
}
/* Enhancement-64: publish the reference resistance of the ports as
a vector, so the Touchstone writers can label the file header
(`# Hz S RI R <Rbase>`) without user intervention. */
SPfrontEnd->IFnewUid(ckt, &(nameList[numNames++]), NULL, "Rbase", UID_OTHER, NULL);
// Add noise related output, if needed
if (job->SPdoNoise)
{