Event nodes can be plotted with offset by "iplot", like the

digitop option for "plot".  Offsets may be explicit or automatic with "-o".
A variable to set the offset is shared with "plot".  In plotcurv.c
suppress some warnings that should not apply to event nodes and do
not falsely claim that some vectors have only one x-value.
This commit is contained in:
Giles Atkinson 2025-03-29 19:08:32 +00:00 committed by Holger Vogt
parent 1d435daed7
commit cd6784a079
6 changed files with 180 additions and 62 deletions

View File

@ -10,6 +10,7 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
#include "ngspice/ngspice.h" #include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h" #include "ngspice/cpdefs.h"
#include "ngspice/ftedefs.h" #include "ngspice/ftedefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/dvec.h" #include "ngspice/dvec.h"
#include "ngspice/ftedebug.h" #include "ngspice/ftedebug.h"
#include "breakp.h" #include "breakp.h"
@ -209,7 +210,10 @@ com_trce(wordlist *wl)
} }
/* Incrementally plot a value. This is just like trace. */ /* Incrementally plot values. This is just like trace.
* Nodes may be specified with an offset, as name+number, as that is useful
* for separating the graphs of digital nodes. It will be ignored for analogue.
*/
void void
com_iplot(wordlist *wl) com_iplot(wordlist *wl)
@ -224,12 +228,13 @@ com_iplot(wordlist *wl)
return; return;
} }
/* settrace(wl, VF_PLOT); */
struct dbcomm *d, *td, *currentdb = NULL; struct dbcomm *d, *td, *currentdb = NULL;
double window = 0.0; double window = 0.0;
int initial_steps = IPOINTMIN; #ifdef XSPICE
int event_auto_incr = 0;
#endif
char *s; char *s;
int initial_steps = IPOINTMIN;
/* Look for "-w window-size" at the front, indicating a windowed iplot /* Look for "-w window-size" at the front, indicating a windowed iplot
* or "-d steps" to set the initial delay before the window appears. * or "-d steps" to set the initial delay before the window appears.
@ -254,6 +259,12 @@ com_iplot(wordlist *wl)
wl = wl->wl_next; wl = wl->wl_next;
if (wl) if (wl)
initial_steps = atoi(wl->wl_word); initial_steps = atoi(wl->wl_word);
#ifdef XSPICE
} else if (wl->wl_word[1] == 'o' && !wl->wl_word[2]) {
/* Automatically offset traces for event nodes. */
event_auto_incr = 1;
#endif
} else { } else {
break; break;
} }
@ -269,8 +280,10 @@ com_iplot(wordlist *wl)
d = TMALLOC(struct dbcomm, 1); d = TMALLOC(struct dbcomm, 1);
d->db_analysis = NULL; d->db_analysis = NULL;
d->db_number = debugnumber++; d->db_number = debugnumber++;
d->db_iteration = event_auto_incr ? DB_AUTO_OFFSET : DB_NORMAL;
d->db_op = initial_steps; // Field re-use d->db_op = initial_steps; // Field re-use
d->db_value1 = window; // Field re-use d->db_value1 = window; // Field re-use
if (eq(s, "all")) { if (eq(s, "all")) {
d->db_type = DB_IPLOTALL; d->db_type = DB_IPLOTALL;
} else { } else {
@ -278,8 +291,14 @@ com_iplot(wordlist *wl)
d->db_nodename1 = copy(s); d->db_nodename1 = copy(s);
} }
tfree(s);/*DG: avoid memory leak */ tfree(s);/*DG: avoid memory leak */
d->db_also = currentdb;
/* Chain in expected order. */
if (currentdb)
td->db_also = d;
else
currentdb = d; currentdb = d;
td = d;
wl = wl->wl_next; wl = wl->wl_next;
} }
@ -388,7 +407,10 @@ void
dbfree1(struct dbcomm *d) dbfree1(struct dbcomm *d)
{ {
tfree(d->db_nodename1); tfree(d->db_nodename1);
if (d->db_type != DB_IPLOT && d->db_type != DB_IPLOTALL &&
d->db_type != DB_DEADIPLOT) {
tfree(d->db_nodename2); tfree(d->db_nodename2);
}
if (d->db_also) if (d->db_also)
dbfree(d->db_also); dbfree(d->db_also);
tfree(d); tfree(d);

View File

@ -428,7 +428,7 @@ struct comm spcp_coms[] = {
{ "iplot", com_iplot, TRUE, TRUE, { "iplot", com_iplot, TRUE, TRUE,
{ 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS, { 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS,
NULL, NULL,
"[-w width] [-s initial_steps] [all] [node ...] : Incrementally plot nodes." } , "[-w width] [-d initial_steps] [-o] [all] [node ...] : Incrementally plot nodes." } ,
{ "status", com_sttus, TRUE, FALSE, { "status", com_sttus, TRUE, FALSE,
{ 0, 0, 0, 0 }, E_DEFHMASK, 0, 0, { 0, 0, 0, 0 }, E_DEFHMASK, 0, 0,
NULL, NULL,
@ -897,10 +897,6 @@ struct comm nutcp_coms[] = {
{ 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS, { 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS,
NULL, NULL,
"[all] [node ...] : Save a spice output." } , "[all] [node ...] : Save a spice output." } ,
{ "iplot", NULL, TRUE, TRUE,
{ 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS,
NULL,
"[all] [node ...] : Incrementally plot a node." } ,
{ "status", NULL, TRUE, FALSE, { "status", NULL, TRUE, FALSE,
{ 0, 0, 0, 0 }, E_DEFHMASK, 0, 0, { 0, 0, 0, 0 }, E_DEFHMASK, 0, 0,
NULL, NULL,

View File

@ -162,7 +162,6 @@ int gr_init(double *xlims, double *ylims, /* The size of the screen. */
/* restore background color from previous graph, e.g. for zooming, /* restore background color from previous graph, e.g. for zooming,
it will be used in NewViewport(graph) */ it will be used in NewViewport(graph) */
if (prevgraph > 0) { if (prevgraph > 0) {
pgraph = FindGraph(prevgraph); pgraph = FindGraph(prevgraph);
if (pgraph) if (pgraph)
@ -958,7 +957,7 @@ static int iplot(struct plot *pl, struct dbcomm *db)
for (v = pl->pl_dvecs; v; v = v->v_next) { for (v = pl->pl_dvecs; v; v = v->v_next) {
if (v->v_flags & VF_PLOT) { if (v->v_flags & VF_PLOT) {
gr_start_internal(v, FALSE); gr_start_internal(v, FALSE);
ft_graf(v, xs, TRUE); ft_graf(v, v->v_scale ? v->v_scale : xs, TRUE);
} }
} }
inited = 1; inited = 1;
@ -1119,7 +1118,7 @@ static int iplot(struct plot *pl, struct dbcomm *db)
last = v->v_length - 1; last = v->v_length - 1;
if (len > 2 && if (len > 2 &&
v->v_scale->v_realdata[last] < v->v_scale->v_realdata[last] <=
xs->v_realdata[len - 1]) { xs->v_realdata[len - 1]) {
/* No recent additions to this vector: /* No recent additions to this vector:
* draw/extend horizontal line showing last value. * draw/extend horizontal line showing last value.
@ -1202,6 +1201,11 @@ static void setflag(struct plot *plot, struct dbcomm *db,
} }
for (dc = db; dc; dc = dc->db_also) { for (dc = db; dc; dc = dc->db_also) {
if (db->db_type == DB_IPLOT) { /* Vector cached in db_nodename2. */
v = (struct dvec *)dc->db_nodename2;
if (!v)
continue;
} else {
if (dc->db_nodename1 == NULL) if (dc->db_nodename1 == NULL)
continue; continue;
v = vec_fromplot(dc->db_nodename1, plot); v = vec_fromplot(dc->db_nodename1, plot);
@ -1209,10 +1213,11 @@ static void setflag(struct plot *plot, struct dbcomm *db,
if (!eq(dc->db_nodename1, "0") && value) { if (!eq(dc->db_nodename1, "0") && value) {
fprintf(cp_err, "Warning: node %s non-existent in %s.\n", fprintf(cp_err, "Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name); dc->db_nodename1, plot->pl_name);
/* note: XXX remove it from dbs, so won't get further errors */ /* note: XXX remove it from dbs, so no further errors. */
} }
continue; continue;
} }
}
if (value) if (value)
v->v_flags |= mode; v->v_flags |= mode;
else else
@ -1258,13 +1263,12 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val,
{ {
struct dbcomm *db = (struct dbcomm *)ctx; struct dbcomm *db = (struct dbcomm *)ctx;
struct dvec *v; struct dvec *v;
double value;
int last; int last;
if (db->db_type == DB_DEADIPLOT) if (db->db_type == DB_DEADIPLOT)
return MIF_TRUE; return MIF_TRUE;
v = vec_fromplot(db->db_nodename1, plot_cur); v = (struct dvec *)db->db_nodename2; // Struct member re-use.
if (!v || !v->v_scale)
return MIF_TRUE;
/* Extend vectors. */ /* Extend vectors. */
@ -1272,7 +1276,8 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val,
AddRealValueToVector(v->v_scale, when); AddRealValueToVector(v->v_scale, when);
AddRealValueToVector(v->v_scale, when); AddRealValueToVector(v->v_scale, when);
AddRealValueToVector(v, v->v_realdata[last]); AddRealValueToVector(v, v->v_realdata[last]);
AddRealValueToVector(v, val->rvalue); value = val->rvalue + db->db_value2; // Apply any plotting offset.
AddRealValueToVector(v, value);
if (db->db_graphid) { if (db->db_graphid) {
GRAPH *gr; GRAPH *gr;
@ -1286,20 +1291,21 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val,
gr_point(v, when, v->v_realdata[last], gr_point(v, when, v->v_realdata[last],
v->v_scale->v_realdata[last], v->v_realdata[last], v->v_scale->v_realdata[last], v->v_realdata[last],
last > 0); last > 0);
gr_point(v, when, val->rvalue, when, v->v_realdata[last], 1); gr_point(v, when, value, when, v->v_realdata[last], 1);
if (is_last) { if (is_last) {
struct dvec *xs; struct dvec *xs;
/* Extend horizontally to the end of the time-step. */ /* Extend horizontally to the end of the time-step. */
xs = v->v_plot->pl_scale; xs = v->v_plot->pl_scale;
gr_point(v, xs->v_realdata[xs->v_length - 1], val->rvalue, gr_point(v, xs->v_realdata[xs->v_length - 1], value,
when, val->rvalue, 1); when, value, 1);
} }
LC_flush(); LC_flush();
#ifdef LINE_COMPRESSION_CHECKS #ifdef LINE_COMPRESSION_CHECKS
LC.dv = NULL; // ... and suppress warnings. LC.dv = NULL; // ... and suppress warnings.
#endif #endif
DevUpdate();
PopGraphContext(); PopGraphContext();
} }
} }
@ -1324,10 +1330,26 @@ void gr_iplot(struct plot *plot)
for (db = dbs; db; db = db->db_next) { for (db = dbs; db; db = db->db_next) {
if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) { if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) {
#ifdef XSPICE #ifdef XSPICE
if (db->db_iteration == 0) { if (db->db_iteration > 0) {
double event_node_offset = 0, event_node_spacing;
struct dvec *v; struct dvec *v;
/* First call: find any XSPICE event nodes in the node /* First call: set up event nodes spacing. */
if (db->db_iteration == DB_AUTO_OFFSET) {
/* Magic value: automatically offset traces for
*event nodes.
*/
if (!cp_getvar("event_node_spacing", CP_REAL,
&event_node_spacing, 0)) {
event_node_spacing = 1.5;
}
} else {
event_node_spacing = 0;
}
/* Find any XSPICE event nodes in the node
* list and set up plotting. There is a parallel path * list and set up plotting. There is a parallel path
* for pushing new event values into their corresponding * for pushing new event values into their corresponding
* vectors and plotting them. * vectors and plotting them.
@ -1335,6 +1357,7 @@ void gr_iplot(struct plot *plot)
for (dc = db; dc; dc = dc->db_also) { for (dc = db; dc; dc = dc->db_also) {
struct dbcomm *dd; struct dbcomm *dd;
char *offp, save_sign;
int dup = 0; int dup = 0;
if (dc->db_nodename1 == NULL) if (dc->db_nodename1 == NULL)
@ -1351,14 +1374,62 @@ void gr_iplot(struct plot *plot)
if (dup) if (dup)
continue; continue;
/* Check for a nodename that is an expression. */
offp = strchr(dc->db_nodename1, '+');
if (!offp)
offp = strchr(dc->db_nodename1, '-');
if (offp > dc->db_nodename1) {
save_sign = *offp;
*offp = '\0'; // Trim to bare name.
}
v = vec_fromplot(dc->db_nodename1, plot); v = vec_fromplot(dc->db_nodename1, plot);
if (!v) { if (v) {
dc->db_nodename2 = (char *)v; // Save link to vector.
} else {
fprintf(cp_err, fprintf(cp_err,
"Warning: node %s non-existent in %s.\n", "Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name); dc->db_nodename1, plot->pl_name);
} }
if (v && (v->v_flags & VF_EVENT_NODE)) { if (v && (v->v_flags & VF_EVENT_NODE)) {
/* Ask event simulator to call back with new values. */
EVTnew_value_call(dc->db_nodename1, new_event,
Evt_Cbt_Plot, dc);
if (offp > dc->db_nodename1) {
*offp = save_sign;
dc->db_value2 = atof(offp); // Offset to value.
event_node_offset = // New auto-offset.
dc->db_value2 + event_node_spacing;
} else if (event_node_spacing) {
char new_name[256];
/* Add offset to vector names.
* Ugly, but only here can event nodes
* be identified.
*/
snprintf(new_name, sizeof new_name, "%s+%g",
dc->db_nodename1, event_node_offset);
tfree(v->v_name);
v->v_name = copy(new_name);
dc->db_value2 = event_node_offset;
event_node_offset += event_node_spacing;
}
if (dc->db_value2) {
int i;
/* Adjust existing values. */
for (i = 0; i < v->v_length; ++i)
v->v_realdata[i] += dc->db_value2;
}
if ((v->v_flags & VF_PERMANENT) == 0) { if ((v->v_flags & VF_PERMANENT) == 0) {
vec_new(v); vec_new(v);
v->v_flags |= VF_PERMANENT; // Make it findable. v->v_flags |= VF_PERMANENT; // Make it findable.
@ -1367,11 +1438,13 @@ void gr_iplot(struct plot *plot)
vec_new(v->v_scale); vec_new(v->v_scale);
} }
} }
EVTnew_value_call(dc->db_nodename1, new_event, } else if (offp) {
Evt_Cbt_Plot, dc); fprintf(stderr,
"Offset (%s) ignored for analog node %s\n",
offp, dc->db_nodename1);
} }
} }
db->db_iteration = 1; db->db_iteration = 0;
} }
#endif #endif
if (db->db_graphid) { if (db->db_graphid) {
@ -1486,6 +1559,7 @@ void gr_end_iplot(void)
prev = NULL; prev = NULL;
for (db = dbs; db; prev = db, db = next) { for (db = dbs; db; prev = db, db = next) {
db->db_nodename2 = NULL; // Forget link.
next = db->db_next; next = db->db_next;
if (db->db_type == DB_DEADIPLOT) { if (db->db_type == DB_DEADIPLOT) {
if (db->db_graphid) { if (db->db_graphid) {
@ -1498,12 +1572,12 @@ void gr_end_iplot(void)
} }
} }
else if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) { else if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) {
db->db_iteration = 0; // Reset XSPICE event plotting db->db_iteration = DB_NORMAL; // Reset XSPICE event plotting
if (db->db_graphid) { if (db->db_graphid) {
/* get private copy of dvecs */ /* get private copy of dvecs */
graph = FindGraph(db->db_graphid); graph = FindGraph(db->db_graphid);
link = graph->plotdata; link = graph->plotdata;
while (link) { while (link) {

View File

@ -20,7 +20,7 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
static void plotinterval(struct dvec *v, double lo, double hi, register double *coeffs, static void plotinterval(struct dvec *v, double lo, double hi, register double *coeffs,
int degree, bool rotated); int degree, bool rotated);
static int get_xdirection(struct dvec *xs, int len, bool mn); static int get_xdirection(struct dvec *xs, int len, bool mn, bool analog);
/* Plot the vector v, with scale xs. If we are doing curve-fitting, then /* Plot the vector v, with scale xs. If we are doing curve-fitting, then
* do some tricky stuff. * do some tricky stuff.
@ -90,11 +90,11 @@ ft_graf(struct dvec *v, struct dvec *xs, bool nostart)
if (xs) { if (xs) {
/* Check vector lengths. */ /* Check vector lengths. */
if (v->v_length != xs->v_length) { if (v->v_length != xs->v_length && !v->v_scale) {
fprintf(stderr, fprintf(stderr,
"Warning: length of vector %s and its scale %s do " "Warning: length of vector %s (%d) and its scale %s (%d) "
"not match, plot may be truncated!\n", "do not match, plot may be truncated!\n",
v->v_name, xs->v_name); v->v_name, v->v_length, xs->v_name, xs->v_length);
} }
length = MIN(v->v_length, xs->v_length); length = MIN(v->v_length, xs->v_length);
} else { } else {
@ -146,7 +146,8 @@ ft_graf(struct dvec *v, struct dvec *xs, bool nostart)
Then everything is plotted. */ Then everything is plotted. */
bool mono = (currentgraph->plottype != PLOT_RETLIN); bool mono = (currentgraph->plottype != PLOT_RETLIN);
int dir = get_xdirection(xs, length, mono); int dir = get_xdirection(xs, length, mono,
!(v->v_flags & VF_EVENT_NODE));
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
dx = isreal(xs) ? xs->v_realdata[i] : dx = isreal(xs) ? xs->v_realdata[i] :
realpart(xs->v_compdata[i]); realpart(xs->v_compdata[i]);
@ -361,7 +362,7 @@ plotinterval(struct dvec *v, double lo, double hi, register double *coeffs, int
If more than 10% of the data points deviate from the majority direction, issue a warning, If more than 10% of the data points deviate from the majority direction, issue a warning,
if 'retraceplot' is not set. if 'retraceplot' is not set.
*/ */
static int get_xdirection(struct dvec* xs, int len, bool mn) { static int get_xdirection(struct dvec* xs, int len, bool mn, bool analog) {
int i, dir = 1, inc = 0, dec = 0; int i, dir = 1, inc = 0, dec = 0;
double dx, lx; double dx, lx;
static bool msgsent = FALSE; static bool msgsent = FALSE;
@ -379,17 +380,32 @@ static int get_xdirection(struct dvec* xs, int len, bool mn) {
lx = dx; lx = dx;
} }
if (inc < 2 && dec < 2) /* Event nodes may never change, so no advance is OK. Similarly, vertical
fprintf(stderr, "Warning, (new) x axis seems to have one data point only\n"); * edges may falsely suggest that "retrace" is needed.
*/
if (mn && !msgsent && (((double)inc / len > 0.1 && inc < dec) || ((double)dec / len > 0.1 && inc > dec))) { if (analog) {
fprintf(stderr, "Warning, more than 10%% of scale vector %s data points are not monotonic.\n", xs->v_name); if ((inc + dec) == 0) {
fprintf(stderr, " Please consider using the 'retraceplot' flag to the plot command to plot all data.\n"); fprintf(stderr,
"Warning, (new) x axis (%s) seems to have only one "
"data point: %d points %d increasing %d decreasing.\n",
xs->v_name, xs->v_length, inc, dec);
}
if (mn && !msgsent && (((double)inc / len > 0.1 && inc < dec) ||
((double)dec / len > 0.1 && inc > dec))) {
fprintf(stderr,
"Warning, more than 10%% of scale vector %s data points "
"are not monotonic.\n",
xs->v_name);
fprintf(stderr,
" Please consider using the 'retraceplot' "
"flag to the plot command to plot all data.\n");
msgsent = TRUE; msgsent = TRUE;
} }
}
if (inc < dec) if (inc < dec)
dir = -1; dir = -1;
return dir; return dir;
} }

View File

@ -854,10 +854,14 @@ bool plotit(wordlist *wl, const char *hcopy, const char *devname)
/* Add n * spacing (e.g. 1.5) to digital event node based vectors */ /* Add n * spacing (e.g. 1.5) to digital event node based vectors */
if (digitop) { if (digitop) {
double spacing = 1.5; double spacing;
double nn = 0.; double nn = 0.;
int ii = 0, jj = 0; int ii = 0, jj = 0;
if (!cp_getvar("plot_auto_spacing", CP_REAL, &spacing, 0) ||
spacing < 0.0) {
spacing = 1.5;
}
for (d = vecs; d; d = d->v_link2) { for (d = vecs; d; d = d->v_link2) {
if ((d->v_flags & VF_EVENT_NODE) && if ((d->v_flags & VF_EVENT_NODE) &&
!(d->v_flags & VF_PERMANENT) && !(d->v_flags & VF_PERMANENT) &&
@ -878,11 +882,11 @@ bool plotit(wordlist *wl, const char *hcopy, const char *devname)
if (!ylim) { if (!ylim) {
ylim = TMALLOC(double, 2); ylim = TMALLOC(double, 2);
ylim[0] = 0; ylim[0] = 0;
/* make ylim[1] a multiple of 2*1.5 */ /* make ylim[1] a multiple of 2 * spacing. */
if (jj % 2 == 0) if (jj % 2 == 0)
ylim[1] = nn; ylim[1] = nn;
else else
ylim[1] = nn + 1.5; ylim[1] = nn + spacing;
} }
/* re-scaled plot */ /* re-scaled plot */
else { else {

View File

@ -34,7 +34,13 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
#define DBC_GTE 5 /* >= (ge) */ #define DBC_GTE 5 /* >= (ge) */
#define DBC_LTE 6 /* <= (le) */ #define DBC_LTE 6 /* <= (le) */
/* Below, members db_op and db_value1 are re-purposed by iplot options. */ /* Below, members db_nodename2, db_op, db_iteration and db_value1/2
* are re-purposed by iplot optionsf.
* These definitions are used for db_iteration:
*/
#define DB_NORMAL 1
#define DB_AUTO_OFFSET 2
struct dbcomm { struct dbcomm {
int db_number; /* The number of this debugging command. */ int db_number; /* The number of this debugging command. */