This commit is contained in:
Meisam 2026-07-28 17:28:28 +02:00 committed by Holger Vogt
parent 5970997cc5
commit 1362c5eb80
7 changed files with 232 additions and 4 deletions

View File

@ -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 <f_in> (run `qpss <expr> <f1> <f2> hb` first)\n");
fprintf(cp_err, "Usage: qpac <f_in> | qpac <dec|oct|lin> <N> <fstart> <fstop> (run `qpss ... hb` first)\n");
return;
}
{ /* sweep form: qpac <dec|oct|lin> <N> <fstart> <fstop> -> 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 <dec|oct|lin> <N> <fstart> <fstop>\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 <node>` 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");

View File

@ -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

View File

@ -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 <out> <dec|oct|lin> <N> <fstart> <fstop> -> 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 <output_node> <dec|oct|lin> <N> <fstart> <fstop>\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");

View File

@ -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 | <dec|oct|lin> 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 <dec|oct|lin> 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 <dec|oct|lin> 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,

View File

@ -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 */

View File

@ -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;i<N;i++){ Xr[(size_t)i00*(size_t)N+(size_t)i]=hd->B0r[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;i<Ntot;i++){Xr[i]=0;Xi[i]=0;}
freqs[pt]=freq;
for (i=0;i<N;i++){ double xr=Xr[(size_t)i00*(size_t)N+(size_t)i], xi=Xi[(size_t)i00*(size_t)N+(size_t)i]; data[(size_t)pt*(size_t)N+(size_t)i]=hypot(xr,xi); }
pt++;
if (stepType==0){ if(np<=1) break; freq+=linstep; } else freq*=mult;
}
FREE(Ar); FREE(Ai); FREE(Xr); FREE(Xi);
return pt;
}
/* QPnoise swept: data[pt*2+0]=onoise, data[pt*2+1]=inoise. */
int
QPnoiseSweep(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, i, j, k, i00, pt = 0;
NOISEAN nj; Ndata dn; JOB *oldJob;
double *Psr, *Psi, *Ar, *Ai, *Xr, *Xi, freq, mult, linstep;
if (!hd) { fprintf(stderr, "qpnoise: 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, "qpnoise: bad output node.\n"); return -1; }
for (j=1;j<=N;j++){ double v=0.0; for (k=0;k<Nh;k++) v+=hd->Vr[(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;i<DEVmaxnum;i++) if (DEVices[i]&&DEVices[i]->DEVnoise&&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;k<Nh;k++){ double dens=0.0; size_t blk=(size_t)k*(size_t)N;
for (j=1;j<=N;j++){ ckt->CKTrhs[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;i<DEVmaxnum;i++) if (DEVices[i]&&DEVices[i]->DEVnoise&&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;i<N;i++){ Xr[(size_t)i00*(size_t)N+(size_t)i]=hd->B0r[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;hi<Nh;hi++){
double hr=0.0, hii=0.0, m2;
if (hd->has_src) for (j=0;j<N;j++){ double pr=Psr[(size_t)hi*(size_t)N+(size_t)j], pi=Psi[(size_t)hi*(size_t)N+(size_t)j]; hr+=pr*hd->B0r[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.
*

View File

@ -899,6 +899,7 @@
<ClInclude Include="..\src\frontend\com_qpac.h" />
<ClInclude Include="..\src\frontend\com_qpnoise.h" />
<ClInclude Include="..\src\frontend\com_qpss.h" />
<ClInclude Include="..\src\frontend\com_qpxf.h" />
<ClInclude Include="..\src\frontend\com_rehash.h" />
<ClInclude Include="..\src\frontend\com_set.h" />
<ClInclude Include="..\src\frontend\com_setscale.h" />
@ -1520,6 +1521,7 @@
<ClCompile Include="..\src\frontend\com_qpac.c" />
<ClCompile Include="..\src\frontend\com_qpnoise.c" />
<ClCompile Include="..\src\frontend\com_qpss.c" />
<ClCompile Include="..\src\frontend\com_qpxf.c" />
<ClCompile Include="..\src\frontend\com_rehash.c" />
<ClCompile Include="..\src\frontend\com_set.c" />
<ClCompile Include="..\src\frontend\com_setscale.c" />