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/cpdefs.h"
#include "ngspice/ftedefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/dvec.h"
#include "ngspice/ftedebug.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
com_iplot(wordlist *wl)
@ -224,12 +228,13 @@ com_iplot(wordlist *wl)
return;
}
/* settrace(wl, VF_PLOT); */
struct dbcomm *d, *td, *currentdb = NULL;
double window = 0.0;
int initial_steps = IPOINTMIN;
#ifdef XSPICE
int event_auto_incr = 0;
#endif
char *s;
int initial_steps = IPOINTMIN;
/* Look for "-w window-size" at the front, indicating a windowed iplot
* or "-d steps" to set the initial delay before the window appears.
@ -254,6 +259,12 @@ com_iplot(wordlist *wl)
wl = wl->wl_next;
if (wl)
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 {
break;
}
@ -269,8 +280,10 @@ com_iplot(wordlist *wl)
d = TMALLOC(struct dbcomm, 1);
d->db_analysis = NULL;
d->db_number = debugnumber++;
d->db_op = initial_steps; // Field re-use
d->db_value1 = window; // Field re-use
d->db_iteration = event_auto_incr ? DB_AUTO_OFFSET : DB_NORMAL;
d->db_op = initial_steps; // Field re-use
d->db_value1 = window; // Field re-use
if (eq(s, "all")) {
d->db_type = DB_IPLOTALL;
} else {
@ -278,8 +291,14 @@ com_iplot(wordlist *wl)
d->db_nodename1 = copy(s);
}
tfree(s);/*DG: avoid memory leak */
d->db_also = currentdb;
currentdb = d;
/* Chain in expected order. */
if (currentdb)
td->db_also = d;
else
currentdb = d;
td = d;
wl = wl->wl_next;
}
@ -388,7 +407,10 @@ void
dbfree1(struct dbcomm *d)
{
tfree(d->db_nodename1);
tfree(d->db_nodename2);
if (d->db_type != DB_IPLOT && d->db_type != DB_IPLOTALL &&
d->db_type != DB_DEADIPLOT) {
tfree(d->db_nodename2);
}
if (d->db_also)
dbfree(d->db_also);
tfree(d);

View File

@ -428,7 +428,7 @@ struct comm spcp_coms[] = {
{ "iplot", com_iplot, TRUE, TRUE,
{ 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS,
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,
{ 0, 0, 0, 0 }, E_DEFHMASK, 0, 0,
NULL,
@ -897,10 +897,6 @@ struct comm nutcp_coms[] = {
{ 0200, 0200, 0200, 0200 }, E_DEFHMASK, 0, LOTS,
NULL,
"[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,
{ 0, 0, 0, 0 }, E_DEFHMASK, 0, 0,
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,
it will be used in NewViewport(graph) */
if (prevgraph > 0) {
pgraph = FindGraph(prevgraph);
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) {
if (v->v_flags & VF_PLOT) {
gr_start_internal(v, FALSE);
ft_graf(v, xs, TRUE);
ft_graf(v, v->v_scale ? v->v_scale : xs, TRUE);
}
}
inited = 1;
@ -1119,7 +1118,7 @@ static int iplot(struct plot *pl, struct dbcomm *db)
last = v->v_length - 1;
if (len > 2 &&
v->v_scale->v_realdata[last] <
v->v_scale->v_realdata[last] <=
xs->v_realdata[len - 1]) {
/* No recent additions to this vector:
* draw/extend horizontal line showing last value.
@ -1202,16 +1201,22 @@ static void setflag(struct plot *plot, struct dbcomm *db,
}
for (dc = db; dc; dc = dc->db_also) {
if (dc->db_nodename1 == NULL)
continue;
v = vec_fromplot(dc->db_nodename1, plot);
if (!v || v->v_plot != plot) {
if (!eq(dc->db_nodename1, "0") && value) {
fprintf(cp_err, "Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name);
/* note: XXX remove it from dbs, so won't get further errors */
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)
continue;
v = vec_fromplot(dc->db_nodename1, plot);
if (!v || v->v_plot != plot) {
if (!eq(dc->db_nodename1, "0") && value) {
fprintf(cp_err, "Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name);
/* note: XXX remove it from dbs, so no further errors. */
}
continue;
}
continue;
}
if (value)
v->v_flags |= mode;
@ -1258,13 +1263,12 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val,
{
struct dbcomm *db = (struct dbcomm *)ctx;
struct dvec *v;
double value;
int last;
if (db->db_type == DB_DEADIPLOT)
return MIF_TRUE;
v = vec_fromplot(db->db_nodename1, plot_cur);
if (!v || !v->v_scale)
return MIF_TRUE;
v = (struct dvec *)db->db_nodename2; // Struct member re-use.
/* 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->v_realdata[last]);
AddRealValueToVector(v, val->rvalue);
value = val->rvalue + db->db_value2; // Apply any plotting offset.
AddRealValueToVector(v, value);
if (db->db_graphid) {
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],
v->v_scale->v_realdata[last], v->v_realdata[last],
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) {
struct dvec *xs;
/* Extend horizontally to the end of the time-step. */
xs = v->v_plot->pl_scale;
gr_point(v, xs->v_realdata[xs->v_length - 1], val->rvalue,
when, val->rvalue, 1);
gr_point(v, xs->v_realdata[xs->v_length - 1], value,
when, value, 1);
}
LC_flush();
#ifdef LINE_COMPRESSION_CHECKS
LC.dv = NULL; // ... and suppress warnings.
#endif
DevUpdate();
PopGraphContext();
}
}
@ -1324,18 +1330,35 @@ void gr_iplot(struct plot *plot)
for (db = dbs; db; db = db->db_next) {
if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) {
#ifdef XSPICE
if (db->db_iteration == 0) {
if (db->db_iteration > 0) {
double event_node_offset = 0, event_node_spacing;
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
* for pushing new event values into their corresponding
* vectors and plotting them.
*/
for (dc = db; dc; dc = dc->db_also) {
struct dbcomm *dd;
int dup = 0;
struct dbcomm *dd;
char *offp, save_sign;
int dup = 0;
if (dc->db_nodename1 == NULL)
continue;
@ -1351,14 +1374,62 @@ void gr_iplot(struct plot *plot)
if (dup)
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);
if (!v) {
if (v) {
dc->db_nodename2 = (char *)v; // Save link to vector.
} else {
fprintf(cp_err,
"Warning: node %s non-existent in %s.\n",
dc->db_nodename1, plot->pl_name);
}
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) {
vec_new(v);
v->v_flags |= VF_PERMANENT; // Make it findable.
@ -1367,11 +1438,13 @@ void gr_iplot(struct plot *plot)
vec_new(v->v_scale);
}
}
EVTnew_value_call(dc->db_nodename1, new_event,
Evt_Cbt_Plot, dc);
} else if (offp) {
fprintf(stderr,
"Offset (%s) ignored for analog node %s\n",
offp, dc->db_nodename1);
}
}
db->db_iteration = 1;
db->db_iteration = 0;
}
#endif
if (db->db_graphid) {
@ -1486,6 +1559,7 @@ void gr_end_iplot(void)
prev = NULL;
for (db = dbs; db; prev = db, db = next) {
db->db_nodename2 = NULL; // Forget link.
next = db->db_next;
if (db->db_type == DB_DEADIPLOT) {
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) {
db->db_iteration = 0; // Reset XSPICE event plotting
db->db_iteration = DB_NORMAL; // Reset XSPICE event plotting
if (db->db_graphid) {
/* get private copy of dvecs */
graph = FindGraph(db->db_graphid);
link = graph->plotdata;
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,
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
* do some tricky stuff.
@ -90,11 +90,11 @@ ft_graf(struct dvec *v, struct dvec *xs, bool nostart)
if (xs) {
/* Check vector lengths. */
if (v->v_length != xs->v_length) {
if (v->v_length != xs->v_length && !v->v_scale) {
fprintf(stderr,
"Warning: length of vector %s and its scale %s do "
"not match, plot may be truncated!\n",
v->v_name, xs->v_name);
"Warning: length of vector %s (%d) and its scale %s (%d) "
"do not match, plot may be truncated!\n",
v->v_name, v->v_length, xs->v_name, xs->v_length);
}
length = MIN(v->v_length, xs->v_length);
} else {
@ -146,7 +146,8 @@ ft_graf(struct dvec *v, struct dvec *xs, bool nostart)
Then everything is plotted. */
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++) {
dx = isreal(xs) ? xs->v_realdata[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 '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;
double dx, lx;
static bool msgsent = FALSE;
@ -379,17 +380,32 @@ static int get_xdirection(struct dvec* xs, int len, bool mn) {
lx = dx;
}
if (inc < 2 && dec < 2)
fprintf(stderr, "Warning, (new) x axis seems to have one data point only\n");
/* Event nodes may never change, so no advance is OK. Similarly, vertical
* 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))) {
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;
if (analog) {
if ((inc + dec) == 0) {
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;
}
}
if (inc < dec)
dir = -1;
return dir;
}

View File

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

View File

@ -34,7 +34,13 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
#define DBC_GTE 5 /* >= (ge) */
#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 {
int db_number; /* The number of this debugging command. */