diff --git a/src/frontend/commands.c b/src/frontend/commands.c index a72560cba..8e2988c56 100644 --- a/src/frontend/commands.c +++ b/src/frontend/commands.c @@ -259,7 +259,7 @@ struct comm spcp_coms[] = { { "eprvcd", EVTprintvcd, FALSE, TRUE, { 040000, 040000, 040000, 040000 }, E_BEGINNING, 1, LOTS, arg_enodes, - "node node ... : Print event values into vcd file." }, + "[-a] [-t timescale] node node ... : Print event values into VCD file." }, { "edisplay", EVTdisplay, FALSE, TRUE, { 040000, 040000, 040000, 040000 }, E_BEGINNING, 0, 0, NULL, diff --git a/src/xspice/evt/evtprint.c b/src/xspice/evt/evtprint.c index 624866257..9d9ccb721 100644 --- a/src/xspice/evt/evtprint.c +++ b/src/xspice/evt/evtprint.c @@ -53,6 +53,8 @@ NON-STANDARD FEATURES #include "ngspice/evtproto.h" +#include "ngspice/fteext.h" + #include #include @@ -466,10 +468,11 @@ get_vcdval(char *xspiceval, char **newval) "0z", "1z", "Uz", "0u", "1u", "Uu" }; + static char *returnmap[] = { "0", "1", "x", "0", "1", "x", - "0", "1", "z", + "z", "z", "z", "0", "1", "x" }; @@ -482,7 +485,7 @@ get_vcdval(char *xspiceval, char **newval) /* is it a real number ? */ retval = INPevaluate(&xspiceval, &err, 1); if (err) { - *newval = copy("unknown"); + *newval = copy(xspiceval); // Assume the node type is coded for this. return 2; } *newval = tprintf("%.16g", retval); @@ -495,6 +498,73 @@ get_vcdval(char *xspiceval, char **newval) #define localtime _localtime64 #endif +/* Function to return a real value to be written to a VCD file. */ + +struct reals { + struct dvec *time; // Scale vector + int v_index, last_i; + double factor; + struct dvec *node_vector[EPRINT_MAXARGS]; // For analog nodes +}; + +static double get_real(int index, double when, struct reals *ctx) +{ + struct dvec *dv; + + if (index < ctx->last_i) { + /* Starting a new pass. */ + + if (!ctx->time) { + ctx->v_index = 0; + ctx->time = vec_get("time"); + if (!ctx->time) { + if (ctx->last_i == EPRINT_MAXARGS) { // First time + fprintf(cp_err, + "ERROR - No vector 'time' in current plot\n"); + } + ctx->node_vector[index] = NULL; // No more calls + return NAN; + } + } + + /* Advance the vector index. */ + + while (ctx->v_index < ctx->time->v_length && + ctx->time->v_realdata[ctx->v_index++] < when) ; + ctx->v_index--; + + /* Calculate interpolation factor. */ + + if (ctx->v_index + 1 < ctx->time->v_length) { + ctx->factor = (when - ctx->time->v_realdata[ctx->v_index]); + ctx->factor /= (ctx->time->v_realdata[ctx->v_index + 1] - + ctx->time->v_realdata[ctx->v_index]); + if (ctx->factor < 0.0 || ctx->factor >= 1.0) + ctx->factor = 0.0; // Rounding + } else { + ctx->factor = 0.0; + } + } + + /* Return interpolated value. */ + + ctx->last_i = index; + dv = ctx->node_vector[index]; + if (ctx->v_index < dv->v_length) { + if (ctx->factor == 0.0) { + return dv->v_realdata[ctx->v_index]; + } else { + return dv->v_realdata[ctx->v_index] + + ctx->factor * + (dv->v_realdata[ctx->v_index + 1] - + dv->v_realdata[ctx->v_index]); + } + } else { + ctx->node_vector[index] = NULL; // No more calls + return dv->v_realdata[dv->v_length - 1]; + } +} + /* * A simple vcd file printer. * command 'eprvcd a0 a1 a2 b0 b1 b2 clk > myvcd.vcd' @@ -510,16 +580,23 @@ EVTprintvcd(wordlist *wl) { int i; int nargs; + int timesteps = 0, tspower = -1; wordlist *w; + struct reals ctx; + + double out_time, last_out_time; + + char *node_name[EPRINT_MAXARGS]; - int node_index[EPRINT_MAXARGS]; - int udn_index[EPRINT_MAXARGS]; + int node_index[EPRINT_MAXARGS]; + int udn_index[EPRINT_MAXARGS]; Evt_Node_t *node_data[EPRINT_MAXARGS]; char *node_value[EPRINT_MAXARGS]; char *old_node_value[EPRINT_MAXARGS]; - char node_ident[EPRINT_MAXARGS + 1]; + char node_ident[EPRINT_MAXARGS + 1]; + char vbuf[24][2][EPRINT_MAXARGS]; // Analog value strings CKTcircuit *ckt; @@ -527,19 +604,37 @@ EVTprintvcd(wordlist *wl) Mif_Boolean_t more; - double step = 0.0; double next_step; double this_step; char *value; + /* Check for the "-a" option (output analog values at timesteps) + * and "-t nn": specifies the VCD timestep as a power of ten. + */ + + while (wl && wl->wl_word[0] == '-') { + if (wl->wl_word[1] == 'a' && !wl->wl_word[2]) { + timesteps = 1; + } else if (wl->wl_word[1] == 't' && !wl->wl_word[2]) { + wl = wl->wl_next; + if (wl) + tspower = atoi(wl->wl_word); + else + break; + } else { + break; + } + wl = wl->wl_next; + } + /* Count the number of arguments to the command */ nargs = 0; for (w = wl; w; w = w->wl_next) nargs++; if (nargs < 1) { - printf("Usage: eprvcd ...\n"); + printf("Usage: eprvcd [-a] ...\n"); return; } if (nargs > EPRINT_MAXARGS) { @@ -560,17 +655,41 @@ EVTprintvcd(wordlist *wl) node_table = ckt->evt->info.node_table; /* Get data for each argument */ + w = wl; for (i = 0; i < nargs; i++) { node_name[i] = w->wl_word; node_index[i] = get_index(node_name[i]); - if (node_index[i] < 0) { - fprintf(cp_err, "ERROR - Node %s is not an event node.\n", node_name[i]); - return; - } - udn_index[i] = node_table[node_index[i]]->udn_index; - node_data[i] = ckt->evt->data.node->head[node_index[i]]; + if (node_index[i] >= 0) { + udn_index[i] = node_table[node_index[i]]->udn_index; + node_data[i] = ckt->evt->data.node->head[node_index[i]]; + ctx.node_vector[i] = NULL; + } else { + struct pnode *pn; + struct dvec *dv; + wordlist *save; + + /* Is it an analog parameter/node expression? + * The whole expression must be a single word (no spaces). + */ + + save = w->wl_next; + w->wl_next = NULL; + pn = ft_getpnames_quotes(w, TRUE); + w->wl_next = save; + if (pn) { + dv = ft_evaluate(pn); + free_pnode(pn); + } else { + dv = NULL; + } + if (!dv) { + fprintf(cp_err, "ERROR - Node %s not parsed.\n", node_name[i]); + return; + } + ctx.node_vector[i] = dv; + } node_value[i] = ""; w = w->wl_next; } @@ -606,43 +725,95 @@ EVTprintvcd(wordlist *wl) /* get the sim time resolution based on tstep */ char *unit; - double scale; - double tstep = ckt->CKTstep; + double scale, tick; + + if (tspower >= 0) { + /* VCD timestep set by "-t" option. */ + + if (tspower == 0) { + unit = "s"; + scale = 1.0; + } else if (tspower < 4) { + unit = "ms"; + tspower = 3 - tspower; + scale = 1e3 * exp10((double)-tspower); + } else if (tspower < 7) { + unit = "us"; + tspower = 6 - tspower; + scale = 1e6 * exp10((double)-tspower); + } else if (tspower < 10) { + unit = "ns"; + tspower = 9 - tspower; + scale = 1e9 * exp10((double)-tspower); + } else if (tspower < 13) { + unit = "ps"; + tspower = 12 - tspower; + scale = 1e12 * exp10((double)-tspower); + } else if (tspower < 16) { + unit = "fs"; + tspower = 15 - tspower; + scale = 1e15 * exp10((double)-tspower); + } else { // 1 fS is the bottom. + unit = "fs"; + tspower = 0; + scale = 1e15; + } + out_printf("$timescale %g %s $end\n", exp10((double)tspower), unit); + } else { + double tstep = ckt->CKTstep; - /* if selected time step is down to [ms] then report time at [us] etc., always with one level higher resolution */ - if (tstep >= 1e-3) { - unit = "us"; - scale = 1e6; - } - else if (tstep >= 1e-6) { - unit = "ns"; - scale = 1e9; - } - else if (tstep >= 1e-9) { - unit = "ps"; - scale = 1e12; - } - else { - unit = "fs"; - scale = 1e15; + /* Use the simulation time step. If the selected time step + * is down to [ms] then report time at [us] etc., + * always with one level higher resolution. + */ + + if (tstep >= 1e-3) { + unit = "us"; + scale = 1e6; + } + else if (tstep >= 1e-6) { + unit = "ns"; + scale = 1e9; + } + else if (tstep >= 1e-9) { + unit = "ps"; + scale = 1e12; + } else { + unit = "fs"; + scale = 1e15; + } + out_printf("$timescale 1 %s $end\n", unit); } - out_printf("$timescale 1 %s $end\n", unit); + tick = 1.0 / scale; /* Scan the node data. Go for printing using $dumpvars for the initial values. Also, determine if there is more data following it and if so, what the next step is. */ + + ctx.time = NULL; + ctx.last_i = EPRINT_MAXARGS; // Indicate restart more = MIF_FALSE; next_step = 1e30; for (i = 0; i < nargs; i++) { - step = node_data[i]->step; - g_evt_udn_info[udn_index[i]]->print_val - (node_data[i]->node_value, "all", &value); - old_node_value[i] = node_value[i] = value; - node_data[i] = node_data[i]->next; - if (node_data[i]) { - more = MIF_TRUE; - if (next_step > node_data[i]->step) - next_step = node_data[i]->step; + if (ctx.node_vector[i]) { + /* Analog node or expression. */ + + sprintf(vbuf[0][i], "%.16g", get_real(i, 0.0, &ctx)); + node_value[i] = vbuf[0][i]; + old_node_value[i] = vbuf[1][i]; + strcpy(vbuf[1][i], vbuf[0][i]); + } else { + /* This must return a pointer to a statically-allocated string. */ + + g_evt_udn_info[udn_index[i]]->print_val + (node_data[i]->node_value, "all", &value); + node_data[i] = node_data[i]->next; + old_node_value[i] = node_value[i] = value; + if (node_data[i]) { + more = MIF_TRUE; + if (next_step > node_data[i]->step) + next_step = node_data[i]->step; + } } } @@ -658,7 +829,6 @@ EVTprintvcd(wordlist *wl) } out_printf("$enddefinitions $end\n"); - out_printf("#%lld\n", (unsigned long long)(step * scale)); /* first set of data for initialization or if only op has been calculated */ @@ -676,38 +846,92 @@ EVTprintvcd(wordlist *wl) out_printf("$end\n"); /* While there is more data, get the next values and print */ - while (more) { - more = MIF_FALSE; + last_out_time = 0.0; + while (more || + (timesteps && ctx.time && ctx.v_index + 1 < ctx.time->v_length)) { + int got_one; + this_step = next_step; - next_step = 1e30; - for (i = 0; i < nargs; i++) - if (node_data[i]) { - if (node_data[i]->step == this_step) { - g_evt_udn_info[udn_index[i]]->print_val - (node_data[i]->node_value, "all", &value); - node_value[i] = value; - node_data[i] = node_data[i]->next; - } - if (node_data[i]) { - more = MIF_TRUE; - if (next_step > node_data[i]->step) - next_step = node_data[i]->step; + if (timesteps && ctx.time && ctx.v_index + 1 < ctx.time->v_length && + (ctx.time->v_realdata[ctx.v_index + 1] < this_step || + (timesteps && !more))) { + + /* Analogue output at each time step, skipping if they would + * appear simulataneous in the output. + */ + + out_time = ctx.time->v_realdata[ctx.v_index + 1]; + if (out_time - last_out_time < tick) { + ++ctx.v_index; + continue; + } + + for (i = 0; i < nargs; i++) { + if (ctx.node_vector[i]) + sprintf(node_value[i], "%.16g", + get_real(i, out_time, &ctx)); + } + } else { + /* Process next event. */ + + out_time = this_step; + more = MIF_FALSE; + next_step = 1e30; + for (i = 0; i < nargs; i++) { + if (ctx.node_vector[i]) { + /* Analog node or expression. */ + + sprintf(node_value[i], "%.16g", + get_real(i, this_step, &ctx)); + } else if (node_data[i]) { + if (node_data[i]->step == this_step) { + g_evt_udn_info[udn_index[i]]->print_val + (node_data[i]->node_value, "all", &value); + node_value[i] = value; + node_data[i] = node_data[i]->next; + } + if (node_data[i]) { + more = MIF_TRUE; + if (next_step > node_data[i]->step) + next_step = node_data[i]->step; + } } } + } - /* timestamp */ - out_printf("#%lld\n", (unsigned long long)(this_step * scale)); /* print only values that have changed */ - for (i = 0; i < nargs; i++) { + + for (i = 0, got_one = 0; i < nargs; i++) { if (!eq(old_node_value[i], node_value[i])) { char *buf; + + if (!got_one) { + /* timestamp */ + + out_printf("#%lld\n", + (unsigned long long)(out_time * scale)); + last_out_time = out_time;; + got_one = 1; + } + if (get_vcdval(node_value[i], &buf) == 1) out_printf("r%s %c\n", buf, node_ident[i]); else out_printf("%s%c\n", buf, node_ident[i]); - old_node_value[i] = node_value[i]; + + if (ctx.node_vector[i]) { + char *t; + + /* Swap buffers. */ + + t = old_node_value[i]; + old_node_value[i] = node_value[i]; + node_value[i] = t; + } else {; + old_node_value[i] = node_value[i]; + } tfree(buf); } }