From 1362c5eb80dd0543b855cc803a3cb78f0fcd61dc Mon Sep 17 00:00:00 2001 From: Meisam Date: Tue, 28 Jul 2026 17:28:28 +0200 Subject: [PATCH] pa-142 --- src/frontend/com_qpac.c | 71 +++++++++++++++++++- src/frontend/com_qpac.h | 7 ++ src/frontend/com_qpnoise.c | 28 ++++++++ src/frontend/commands.c | 6 +- src/include/ngspice/cktdefs.h | 3 + src/spicelib/analysis/dcpss.c | 119 ++++++++++++++++++++++++++++++++++ visualc/vngspice.vcxproj | 2 + 7 files changed, 232 insertions(+), 4 deletions(-) diff --git a/src/frontend/com_qpac.c b/src/frontend/com_qpac.c index e6d811c25..c16eccabf 100644 --- a/src/frontend/com_qpac.c +++ b/src/frontend/com_qpac.c @@ -16,6 +16,8 @@ dcpss.c); this command parses f_in and runs it. #include "ngspice/fteext.h" #include "ngspice/wordlist.h" #include "ngspice/cpextern.h" +#include "ngspice/dvec.h" +#include "ngspice/sim.h" #include "com_qpac.h" @@ -28,6 +30,45 @@ static double qpacnum(const char *w) return v; } +/* dec/oct/lin -> 1/2/0 (a frequency sweep), else -1 (single frequency) */ +int qp_steptype(const char *w) +{ + if (!strcasecmp(w, "dec")) return 1; + if (!strcasecmp(w, "oct")) return 2; + if (!strcasecmp(w, "lin")) return 0; + return -1; +} + +/* upper bound on the number of sweep points (the analysis returns the exact count) */ +int qp_sweep_maxpts(int stepType, int np, double fstart, double fstop) +{ + if (stepType == 1) return (int)(np * log10(fstop/fstart)) + 3; /* dec */ + if (stepType == 2) return (int)(np * log(fstop/fstart)/log(2.0)) + 3; /* oct */ + return np + 1; /* lin */ +} + +/* build an ngspice plot named `plotname` with a frequency scale + nvec real data + * vectors (data is point-major: data[p*nvec + k]). Shared by qpac/qpnoise/qpxf sweeps. */ +void qp_emit_plot(const char *plotname, const char *title, double *freqs, int npts, + char **vnames, int nvec, double *data) +{ + struct plot *pl = plot_alloc((char *) plotname); + struct dvec *sc; + int p, k; + pl->pl_name = copy((char *) title); + pl->pl_title = copy((char *) title); + plot_new(pl); + plot_setcur(pl->pl_typename); + sc = dvec_alloc(copy("frequency"), SV_FREQUENCY, (short)(VF_REAL | VF_PERMANENT), npts, NULL); + for (p = 0; p < npts; p++) sc->v_realdata[p] = freqs[p]; + vec_new(sc); /* first permanent vector -> scale */ + for (k = 0; k < nvec; k++) { + struct dvec *v = dvec_alloc(copy(vnames[k]), SV_NOTYPE, (short)(VF_REAL | VF_PERMANENT), npts, NULL); + for (p = 0; p < npts; p++) v->v_realdata[p] = data[(size_t)p*(size_t)nvec + (size_t)k]; + vec_new(v); + } +} + void com_qpac(wordlist *wl) { @@ -42,9 +83,37 @@ com_qpac(wordlist *wl) ckt = ft_curckt->ci_ckt; if (!wl || !wl->wl_word) { - fprintf(cp_err, "Usage: qpac (run `qpss hb` first)\n"); + fprintf(cp_err, "Usage: qpac | qpac (run `qpss ... hb` first)\n"); return; } + { /* sweep form: qpac -> plot vs f_in */ + int st = qp_steptype(wl->wl_word); + if (st >= 0) { + double fstart, fstop, *freqs, *data; int np, npts, maxpts, numNames = 0, i; + IFuid *nameList = NULL; char **vn; + if (!wl->wl_next || !wl->wl_next->wl_next || !wl->wl_next->wl_next->wl_next) { + fprintf(cp_err, "Usage: qpac \n"); return; + } + np = (int) qpacnum(wl->wl_next->wl_word); + fstart = qpacnum(wl->wl_next->wl_next->wl_word); + fstop = qpacnum(wl->wl_next->wl_next->wl_next->wl_word); + if (np < 1 || fstart <= 0.0 || fstop < fstart) { fprintf(cp_err, "Error: qpac: bad sweep spec.\n"); return; } + if (CKTnames(ckt, &numNames, &nameList) != OK || numNames < 1) { fprintf(cp_err, "Error: qpac: no nodes.\n"); return; } + maxpts = qp_sweep_maxpts(st, np, fstart, fstop); + freqs = TMALLOC(double, maxpts); + data = TMALLOC(double, (size_t)maxpts * (size_t)numNames); + npts = QPACsweep(ckt, st, np, fstart, fstop, freqs, data); + if (npts > 0) { + vn = TMALLOC(char *, numNames); + for (i = 0; i < numNames; i++) vn[i] = (char *) nameList[i]; + qp_emit_plot("qpac", "QPAC Analysis", freqs, npts, vn, numNames, data); + fprintf(cp_out, "qpac: swept %d points into a new plot (now current); `plot ` to view the per-node response vs f_in.\n", npts); + FREE(vn); + } else fprintf(cp_err, "qpac: sweep did not complete.\n"); + tfree(nameList); FREE(freqs); FREE(data); + return; + } + } f_in = qpacnum(wl->wl_word); if (f_in <= 0.0) { fprintf(cp_err, "Error: qpac: need f_in > 0.\n"); diff --git a/src/frontend/com_qpac.h b/src/frontend/com_qpac.h index bc91afdfa..db8b5018d 100644 --- a/src/frontend/com_qpac.h +++ b/src/frontend/com_qpac.h @@ -4,4 +4,11 @@ /* Enhancement-137: two-tone small-signal QPAC (quasi-periodic AC). */ void com_qpac(wordlist *wl); +/* Enhancement-142: shared sweep helpers (dec/oct/lin classification, point count, and + * the ngspice-plot emitter) used by the qpac/qpnoise/qpxf frequency sweeps. */ +int qp_steptype(const char *w); +int qp_sweep_maxpts(int stepType, int np, double fstart, double fstop); +void qp_emit_plot(const char *plotname, const char *title, double *freqs, int npts, + char **vnames, int nvec, double *data); + #endif diff --git a/src/frontend/com_qpnoise.c b/src/frontend/com_qpnoise.c index 8d9a1bd8d..abf7fcb5e 100644 --- a/src/frontend/com_qpnoise.c +++ b/src/frontend/com_qpnoise.c @@ -69,6 +69,34 @@ com_qpnoise(wordlist *wl) fprintf(cp_err, "Error: qpnoise: unknown output node '%s'.\n", wl->wl_word); return; } + { /* sweep form: qpnoise -> onoise/inoise plot */ + extern int qp_steptype(const char *w); + extern int qp_sweep_maxpts(int stepType, int np, double fstart, double fstop); + extern void qp_emit_plot(const char *plotname, const char *title, double *freqs, int npts, + char **vnames, int nvec, double *data); + int st = qp_steptype(wl->wl_next->wl_word); + if (st >= 0) { + double fstart, fstop, *freqs, *data; int np, npts, maxpts; + char *vn[2] = { (char*)"onoise_spectrum", (char*)"inoise_spectrum" }; + wordlist *w = wl->wl_next->wl_next; + if (!w || !w->wl_next || !w->wl_next->wl_next) { + fprintf(cp_err, "Usage: qpnoise \n"); return; + } + np = (int) qpnnum(w->wl_word); + fstart = qpnnum(w->wl_next->wl_word); + fstop = qpnnum(w->wl_next->wl_next->wl_word); + if (np < 1 || fstart <= 0.0 || fstop < fstart) { fprintf(cp_err, "Error: qpnoise: bad sweep spec.\n"); return; } + maxpts = qp_sweep_maxpts(st, np, fstart, fstop); + freqs = TMALLOC(double, maxpts); data = TMALLOC(double, (size_t)maxpts*2); + npts = QPnoiseSweep(ckt, outNode, st, np, fstart, fstop, freqs, data); + if (npts > 0) { + qp_emit_plot("qpnoise", "QPnoise Analysis", freqs, npts, vn, 2, data); + fprintf(cp_out, "qpnoise: swept %d points into a new plot (now current); `plot onoise_spectrum inoise_spectrum` to view.\n", npts); + } else fprintf(cp_err, "qpnoise: sweep did not complete.\n"); + FREE(freqs); FREE(data); + return; + } + } f_in = qpnnum(wl->wl_next->wl_word); if (f_in <= 0.0) { fprintf(cp_err, "Error: qpnoise: need f_in > 0.\n"); diff --git a/src/frontend/commands.c b/src/frontend/commands.c index 7529960e7..5a60ebb50 100644 --- a/src/frontend/commands.c +++ b/src/frontend/commands.c @@ -439,15 +439,15 @@ struct comm spcp_coms[] = { { "qpac", com_qpac, TRUE, FALSE, /* Enhancement-137 */ { 040, 040, 040, 040 }, E_DEFHMASK, 1, LOTS, NULL, - "f_in : two-tone small-signal QPAC -- response at sidebands f_in+k1f1+k2f2 around the `qpss ... hb` operating point." }, + "f_in | N fstart fstop : two-tone QPAC -- sideband response (single f) or swept plot vs f_in, around the `qpss ... hb` operating point." }, { "qpxf", com_qpxf, TRUE, FALSE, /* Enhancement-141 */ { 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS, NULL, - "output_node f_in : two-tone QPXF -- transfer from each input sideband f_in+k1f1+k2f2 to the output, around the `qpss ... hb` operating point." }, + "output_node f_in | output_node N fstart fstop : two-tone QPXF -- per-sideband transfer at f_in (or swept xf/xf_conv plot), around the `qpss ... hb` operating point." }, { "qpnoise", com_qpnoise, TRUE, FALSE, /* Enhancement-138 */ { 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS, NULL, - "output_node f_in [cyclo] : two-tone QPnoise -- output/input noise density at f_in, folding device noise over all sidebands around the `qpss ... hb` operating point (`cyclo` = cyclostationary PSD)." }, + "output_node f_in [cyclo] | output_node N fstart fstop : two-tone QPnoise -- output/input noise density at f_in (or swept onoise/inoise plot), around the `qpss ... hb` operating point (`cyclo` = cyclostationary PSD)." }, { "hbosc", com_hbosc, TRUE, FALSE, /* Enhancement-140 */ { 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS, NULL, diff --git a/src/include/ngspice/cktdefs.h b/src/include/ngspice/cktdefs.h index 023550e13..cf625632e 100644 --- a/src/include/ngspice/cktdefs.h +++ b/src/include/ngspice/cktdefs.h @@ -479,6 +479,9 @@ extern int HBanalyze(CKTcircuit *, double f0, int K, int P, int maxiter, double extern int QPSShb(CKTcircuit *, double f1, double f2, int K1, int K2, int P1, int P2, int maxiter, double tol, int verbose); /* E-136 */ extern int QPACanalyze(CKTcircuit *, double f_in, int verbose); /* E-137 */ extern int QPXFanalyze(CKTcircuit *, int outNode, double f_in, int verbose); /* E-141 */ +extern int QPACsweep(CKTcircuit *, int stepType, int np, double fstart, double fstop, double *freqs, double *data); /* E-142 */ +extern int QPnoiseSweep(CKTcircuit *, int outNode, int stepType, int np, double fstart, double fstop, double *freqs, double *data); /* E-142 */ +extern int QPXFsweep(CKTcircuit *, int outNode, int stepType, int np, double fstart, double fstop, double *freqs, double *data); /* E-142 */ extern int QPnoiseAnalyze(CKTcircuit *, int outNode, double f_in, int cyclo, int verbose); /* E-138 / -139 */ extern int HBOSCanalyze(CKTcircuit *, int oscNode, int K, int P, double f0seed, double ampseed, int maxiter, double tol, int verbose); /* E-140 */ extern int PhaseNoiseAnalyze(CKTcircuit *, double fstart, double fstop, int npts, int verbose); /* E-140 */ diff --git a/src/spicelib/analysis/dcpss.c b/src/spicelib/analysis/dcpss.c index 1bbe3a995..3f1ce3177 100644 --- a/src/spicelib/analysis/dcpss.c +++ b/src/spicelib/analysis/dcpss.c @@ -2493,6 +2493,125 @@ QPXFanalyze(CKTcircuit *ckt, int outNode, double f_in, int verbose) } +/* ====================================================================== + * Enhancement-142: input-frequency SWEEP for the two-tone small-signal + * analyses. `qpac`/`qpnoise`/`qpxf` gain a dec|oct|lin sweep of f_in. Each + * point reuses the same single-frequency solve; these fill caller arrays + * (freqs[] + data[], point-major) and the frontend builds the ngspice plot + * (conversion gain / NF / image-rejection curves vs f_in). Return #points. + * ====================================================================== */ + +/* QPAC swept: data[pt*N + node] = |(0,0) response| per node. */ +int +QPACsweep(CKTcircuit *ckt, int stepType, int np, double fstart, double fstop, double *freqs, double *data) +{ + struct qp_harm *hd = qpss_hb_saved; + int N, Ntot, i, i00, pt = 0; + double *Ar, *Ai, *Xr, *Xi, freq, mult, linstep; + (void) ckt; + if (!hd) { fprintf(stderr, "qpac: no QPSS operating point -- run `qpss ... hb` first.\n"); return -1; } + N = hd->N; Ntot = hd->Ntot; i00 = hd->K1*(2*hd->K2+1) + hd->K2; + Ar = TMALLOC(double,(size_t)Ntot*(size_t)Ntot); Ai = TMALLOC(double,(size_t)Ntot*(size_t)Ntot); + Xr = TMALLOC(double, Ntot); Xi = TMALLOC(double, Ntot); + mult = (stepType==1)?pow(10.0,1.0/np):(stepType==2)?pow(2.0,1.0/np):0.0; + linstep = (np>1)?(fstop-fstart)/(np-1):0.0; + for (freq=fstart; freq<=fstop*(1.0+1e-9); ) { + qp_build_matrix(hd, freq, Ar, Ai); + memset(Xr,0,(size_t)Ntot*sizeof(double)); memset(Xi,0,(size_t)Ntot*sizeof(double)); + if (hd->has_src) for (i=0;iB0r[i]; Xi[(size_t)i00*(size_t)N+(size_t)i]=hd->B0i[i]; } + else Xr[(size_t)i00*(size_t)N+0]=1.0; + if (pss_csolve(Ntot,Ar,Ai,Xr,Xi)) for (i=0;iN; Nh = hd->Nh; Ntot = hd->Ntot; i00 = hd->K1*(2*hd->K2+1) + hd->K2; + if (outNode <= 0 || outNode > N) { fprintf(stderr, "qpnoise: bad output node.\n"); return -1; } + for (j=1;j<=N;j++){ double v=0.0; for (k=0;kVr[(size_t)k*(size_t)N+(size_t)(j-1)]; ckt->CKTrhsOld[j]=v; } + ckt->CKTrhsOld[0]=0.0; ckt->CKTmode=(ckt->CKTmode & MODEUIC)|MODEDCOP|MODEINITSMSIG; CKTload(ckt); + memset(&nj,0,sizeof(nj)); nj.NstartFreq=fstart; nj.NstopFreq=fstop; nj.NnumSteps=np; nj.NstpType=stepType; nj.NStpsSm=0; nj.JOBname="qpnoise"; + memset(&dn,0,sizeof(dn)); dn.prtSummary=FALSE; + oldJob=ckt->CKTcurJob; ckt->CKTcurJob=(JOB*)&nj; + for (i=0;iDEVnoise&&ckt->CKThead[i]){ double d=0.0; DEVices[i]->DEVnoise(N_DENS,N_OPEN,ckt->CKThead[i],ckt,&dn,&d); } + Psr=TMALLOC(double,Ntot); Psi=TMALLOC(double,Ntot); + Ar=TMALLOC(double,(size_t)Ntot*(size_t)Ntot); Ai=TMALLOC(double,(size_t)Ntot*(size_t)Ntot); + Xr=TMALLOC(double,Ntot); Xi=TMALLOC(double,Ntot); + mult=(stepType==1)?pow(10.0,1.0/np):(stepType==2)?pow(2.0,1.0/np):0.0; + linstep=(np>1)?(fstop-fstart)/(np-1):0.0; + for (freq=fstart; freq<=fstop*(1.0+1e-9); ) { + double onoise=0.0, gain2=1.0; + if (qp_solve_adjoint(hd, freq, outNode, Psr, Psi)==0) { + dn.freq=freq; dn.delFreq=0.0; dn.prtSummary=FALSE; + for (k=0;kCKTrhs[j]=Psr[blk+(size_t)(j-1)]; ckt->CKTirhs[j]=Psi[blk+(size_t)(j-1)]; } + ckt->CKTrhs[0]=0.0; ckt->CKTirhs[0]=0.0; + for (i=0;iDEVnoise&&ckt->CKThead[i]) DEVices[i]->DEVnoise(N_DENS,N_CALC,ckt->CKThead[i],ckt,&dn,&dens); + onoise+=dens; + } + } + if (hd->has_src){ + qp_build_matrix(hd, freq, Ar, Ai); + memset(Xr,0,(size_t)Ntot*sizeof(double)); memset(Xi,0,(size_t)Ntot*sizeof(double)); + for (i=0;iB0r[i]; Xi[(size_t)i00*(size_t)N+(size_t)i]=hd->B0i[i]; } + if (pss_csolve(Ntot,Ar,Ai,Xr,Xi)==0){ size_t o=(size_t)i00*(size_t)N+(size_t)(outNode-1); gain2=Xr[o]*Xr[o]+Xi[o]*Xi[o]; } + } + freqs[pt]=freq; data[(size_t)pt*2+0]=onoise; data[(size_t)pt*2+1]=onoise/MAX(gain2,N_MINGAIN); + pt++; + if (stepType==0){ if(np<=1) break; freq+=linstep; } else freq*=mult; + } + ckt->CKTcurJob=oldJob; + FREE(Psr);FREE(Psi);FREE(Ar);FREE(Ai);FREE(Xr);FREE(Xi); + return pt; +} + +/* QPXF swept: data[pt*2+0]=|(0,0) transfer|, data[pt*2+1]=total conversion. */ +int +QPXFsweep(CKTcircuit *ckt, int outNode, int stepType, int np, double fstart, double fstop, double *freqs, double *data) +{ + struct qp_harm *hd = qpss_hb_saved; + int N, Nh, Ntot, j, hi, i00, pt = 0; + double *Psr, *Psi, freq, mult, linstep; + (void) ckt; + if (!hd) { fprintf(stderr, "qpxf: no QPSS operating point -- run `qpss ... hb` first.\n"); return -1; } + N = hd->N; Nh = hd->Nh; Ntot = hd->Ntot; i00 = hd->K1*(2*hd->K2+1) + hd->K2; + if (outNode <= 0 || outNode > N) { fprintf(stderr, "qpxf: bad output node.\n"); return -1; } + Psr=TMALLOC(double,Ntot); Psi=TMALLOC(double,Ntot); + mult=(stepType==1)?pow(10.0,1.0/np):(stepType==2)?pow(2.0,1.0/np):0.0; + linstep=(np>1)?(fstop-fstart)/(np-1):0.0; + for (freq=fstart; freq<=fstop*(1.0+1e-9); ) { + double xf0=0.0, conv=0.0; + if (qp_solve_adjoint(hd, freq, outNode, Psr, Psi)==0){ + for (hi=0;hihas_src) for (j=0;jB0r[j]-pi*hd->B0i[j]; hii+=pr*hd->B0i[j]+pi*hd->B0r[j]; } + else { hr=Psr[(size_t)hi*(size_t)N+0]; hii=Psi[(size_t)hi*(size_t)N+0]; } + m2=hr*hr+hii*hii; + if (hi==i00) xf0=sqrt(m2); else conv+=m2; + } + } + freqs[pt]=freq; data[(size_t)pt*2+0]=xf0; data[(size_t)pt*2+1]=sqrt(conv); + pt++; + if (stepType==0){ if(np<=1) break; freq+=linstep; } else freq*=mult; + } + FREE(Psr); FREE(Psi); + return pt; +} + + /* ====================================================================== * Enhancement-140: oscillator phase noise. * diff --git a/visualc/vngspice.vcxproj b/visualc/vngspice.vcxproj index 31a9f2402..13864557e 100644 --- a/visualc/vngspice.vcxproj +++ b/visualc/vngspice.vcxproj @@ -899,6 +899,7 @@ + @@ -1520,6 +1521,7 @@ +