diff --git a/examples/shared/README b/examples/shared/README new file mode 100644 index 000000000..2de2a7350 --- /dev/null +++ b/examples/shared/README @@ -0,0 +1,93 @@ +This directory contains the source code (shx.c) for a simple utility +for exercising the Ngspice shared library API. The path to the ngspice +include file directory must be specified when compiling. When compiling +in a complete source tree: + +cc -I../../src/include/ngspice -o shx shx.c + +or for Windows with VisualC++: + +CL.EXE /O2 /I..\..\src\include\ngspice shx.c + +If a shared library binary package is installed, the inclusion option +may be unnecessary. + +When run, the program dynamically loads the shared library. It is assumed to +be located in the current directory (./libngspice.so or libngspice.DLL). +The full path to the library may be specified by the "-l" option, so if the +library was built by the usual method: + +./shx -l ../../releasesh/src/.libs/libngspice.so + +or + +shx -l ..\..\visualc\sharedspice\Release.x64\ngspice.dll + +If the '-a" option is given, the program exercises some example circuits +and exits. That assumes that the current directory is the one containing this +file. Otherwise the program is interactive and prompts for a command. +If the input line starts with "/" an internal command for exercising the API +will be executed. Other lines are passed to the shared library's command +interpreter. + +These internal commands are available: + + /avec + Lists the vectors in the named plot, using ngSpice_AllVecs(). + /aevt + Lists all XSPICE event nodes, using ngSpice_AllEvtNodes(). + /aplot + Lists all plot names, using ngSpice_AllPlots(). + /bgr + Shows the state of the background thread, using ngSpice_running(). + /cplot + Show the name of the current plot, using ngSpice_CurPlot(). + /dlim + To reduce repetitive output, this command sets a limit for the number + of SendData callbacks that will be reported. The parameter should be + a positive integer, 0 to report all, or -1 for unlimited output. + The initial value is 10. + /elim + Like /dlim but for XSPICE event data callbacks. + /help + Shows a summary of minternal commands. + /lvals + Toggles a flag that causes all node values to be listed for + SendData callbacks. + /reset + Resets Ngspice (call to ngSpice_Reset()) and internal variables. + /sask + Toggles a flag that control interactive prompting for + EXTERNAL source values. As a special case, the /sask command + is accepted at the prompt. + /slim + Like /dlim, but sets an independent limit for reporting callbacks that + request an new value for EXTERNAL sources. + /sramp [new_val] [interval end_val] + Stores data used to genarate an automatic response to EXTERNAL queries. + A single parameter sets a new value for the future (step change), + two values define a ramp to a specified value at a future time, + three values do both. If there is more than one such source, the same + value is used for all. + /vec + Query a spacific data vector, using ngSpice_GVI(). + /xnode + Requests raw event callbacks for an event node. That reports all node + events, not just the value when a timestep ends. Uses ngSpice_Raw_Evt(). + + +Also found here are an example circuit, t.cir, with analog and event nodes +and an EXTERNAL voltage source. File t.shx contains inputs to shx that +run the circuit and use most of the internal commands. Run as: + +./shx < t.shx + +or for Windows + +shx < t.txt + +To view the results, run Ngspice as a program and enter: + + load t.raw + plot v(ctl) v(div) + diff --git a/examples/shared/shx.c b/examples/shared/shx.c new file mode 100644 index 000000000..a3388b7a3 --- /dev/null +++ b/examples/shared/shx.c @@ -0,0 +1,829 @@ +/* +Test file for shared ngspice +Copyright Holger Vogt 2017-2024 +Local commands: Giles Atkinson 2025 +New BSD license + +ngspice library loaded dynamically +simple manual input +*/ + +#include +#include +#include +#include + +#ifndef _MSC_VER +#include +#include + +#else + +#define bool int +#define true 1 +#define false 0 +#define strdup _strdup + +#endif + +#define XSPICE +#include "sharedspice.h" + +typedef void *funptr_t; + +#if defined(__MINGW32__) || defined(__CYGWIN__) || defined(_MSC_VER) + +#undef BOOLEAN +#define LOAD_STRING "libngspice.DLL" + +#include + +void *dlopen (const char *, int); +funptr_t dlsym (void *, const char *); +int dlclose (void *); +char *dlerror (void); +#define RTLD_LAZY 1 /* lazy function call binding */ +#define RTLD_NOW 2 /* immediate function call binding */ +#define RTLD_GLOBAL 4 /* Symbols are externally visible. */ + +static char errstr[128]; + +#else + +#if defined(__APPLE__) +#define LOAD_STRING "./libngspice.dylib" +#else +#define LOAD_STRING "./libngspice.so" +#endif + +#include /* to load libraries*/ +#include + +#endif /* not Windows */ + +#include + +/* pointers to functions exported by ngspice */ + +char ** (*ngSpice_AllEvtNodes_handle)(void); +char ** (*ngSpice_AllPlots_handle)(void); +char ** (*ngSpice_AllVecs_handle)(char*); +int (*ngSpice_Command_handle)(char*); +int (*ngSpice_Circ_handle)(char**); +char * (*ngSpice_CurPlot_handle)(void); +int (*ngSpice_Decode_Evt_handle)(void *, int, + double *, const char **); +pvector_info (*ngSpice_GVI_handle)(char*); +int (*ngSpice_Init_handle)(SendChar*, SendStat*, ControlledExit*, + SendData*, SendInitData*, BGThreadRunning*, + void*); +int (*ngSpice_Init_Evt_handle)(SendEvtData*, SendInitEvtData*, void*); +int (*ngSpice_Init_Sync_handle)(GetVSRCData*, GetISRCData*, + GetSyncData*, int*, void*); +int (*ngSpice_Raw_Evt_handle)(char *, SendRawEvtData *, void *); +int (*ngSpice_Reset_handle)(void); +bool (*ngSpice_running_handle)(void); + +bool no_bg = true; +bool not_yet = true; + +/* Limit output from SendData, SendEvtData and VSRCData callbacks. */ + +static unsigned int sd_limit = 10, sd_count, sd_list; +static unsigned int se_limit = 10, se_count; +static unsigned int sq_limit = 10, sq_count, sq_ask; + +/* Automatic ramp of source query replies. */ + +double sr_last_time = -1, sr_target_time = -1; +double sr_last_val, sr_target_val, sim_time; + +static int cieq(register char *p, register char *s); +static int ciprefix(const char *p, const char *s); +static int getLine(char *prmpt, char *buff, size_t sz); + +/* Callback functions used by ngspice and defined below. */ + +static int +ng_getchar(char* outputreturn, int ident, void* userdata); + +static int +ng_getstat(char* outputreturn, int ident, void* userdata); + +static int +ng_thread_runs(bool noruns, int ident, void* userdata); + +static int ng_rawevt(double, void *, void *, int); + +static ControlledExit ng_exit; +static SendData ng_data; +static SendInitData ng_initdata; +static SendInitEvtData ng_initevtdata; +static SendEvtData ng_evtdata; +static GetVSRCData ng_srcdata; +static GetSyncData ng_syncdata; + +char comd[1024]; +void * ngdllhandle = NULL; + +/* Register call-back functions. */ + +static void register_cbs(void) { + static int z = 0; + char *bad; + int ret; + + ret = ngSpice_Init_handle(ng_getchar, ng_getstat, + ng_exit, ng_data, ng_initdata, ng_thread_runs, + NULL); + if (ret) { + bad = "ngSpice_Init"; + goto fail; + } + ret = ngSpice_Init_Evt_handle(ng_evtdata, ng_initevtdata, NULL); + if (!ret) { + bad = "ngSpice_Init_Evt"; + goto fail; + } + ret = ngSpice_Init_Sync_handle(ng_srcdata, ng_srcdata, ng_syncdata, + &z, NULL); + if (ret == 0) + return; + bad = "ngSpice_Init_Sync"; + fail: + fprintf(stderr, "Init call %s() failed: %d\n", bad, ret); + exit(2); +} + +/* Non-interactive test: run a selection of circuits. */ + +static int auto_test(void) +{ + #define SRC_FMT "source ../%s" /* Assume cd is examples/shared. */ + static const char * const tests[] = { + "transient-noise/shot_ng.cir", + "soi/inv_tr.sp", + "p-to-n-examples/op-test-adi.cir", + "digital/compare/adder_Xspice.cir", + NULL + }; + int ret, i; + char msgbuf[128]; + + for (i = 0; tests[i]; ++i) { + register_cbs(); + snprintf(msgbuf, sizeof msgbuf, "echo run no. %d", i + 1); + ret = ngSpice_Command_handle(msgbuf); + if (ret) + return ret; + snprintf(msgbuf, sizeof msgbuf, SRC_FMT, tests[i]); + ret = ngSpice_Command_handle(msgbuf); + if (ret) + return ret; + ret = ngSpice_Reset_handle(); + if (ret) + return ret; + } + return 0; +} + +static funptr_t getsym(const char *sym) +{ + funptr_t value; + + value = dlsym(ngdllhandle, sym); + if (!value) { + fprintf(stderr, "Ngspice symbol %s was not found: %s\n", + sym, dlerror()); + exit(1); + } + return value; +} + +/* String utilities. */ + +char *skip(char *cp) +{ + while (isspace(*cp)) + ++cp; + return cp; +} + +char *tail(char *cp) +{ + while (*cp && !isspace(*cp)) + ++cp; + return cp; +} + +/* Execute a local command, identified by a leading '/'. */ + +static void help(char *cmd) { + puts("Local commands are:\n" + " /aevt\t\t\tList event nodes.\n" + " /aplot\t\tList plot names.\n" + " /avec \tList vectors in plot.\n" + " /bgr\t\t\tQuery background thread.\n" + " /cplot\t\tShow name of current plot.\n" + " /dlim \t\tSet output limit for SendData CB.\n" + " /elim \t\tSet output limit for SendEvtData CB.\n" + " /help\t\t\tPrint this message.\n" + " /lvals\t\tList node values on data callback (toggle).\n" + " /reset\t\tReset Ngspice.\n" + " /sask\t\t\tAsk for V/ISRC values (toggles).\n" + " /slim \t\tSet output limit for SendxSRCData CB.\n" + " /sramp [new_val] [interval end_val]\n" + "\t\t\tAuto-ramp sources\n" + " /vec \t\tQuery vector.\n" + " /xnode \tRequest raw event callbacks for event node.\n" + "All other input is passed to Ngspice.\n"); +} + +static void aevt(char *cmd) { + char **cpp; + + cpp = ngSpice_AllEvtNodes_handle(); + if (!cpp) + return; + printf("Event nodes:\n"); + while (*cpp) + printf(" %s\n", *cpp++); +} + +static void aplot(char *cmd) { + char **cpp; + + cpp = ngSpice_AllPlots_handle(); + if (!cpp) + return; + printf("Plots:\n"); + while (*cpp) + printf(" %s\n", *cpp++); +} + +static void avec(char *cmd) { + char **cpp; + + cpp = ngSpice_AllVecs_handle(cmd); + if (!cpp) + return; + printf("Vectors in plot %s:\n", cmd); + while (*cpp) + printf(" %s\n", *cpp++); +} + +static void bgr(char *cmd) { + printf("Background thread is %srunning\n", + ngSpice_running_handle() ? "": "not "); +} + +static void cplot(char *cmd) { + printf("Current plot: %s\n", ngSpice_CurPlot_handle()); +} + +static void dlim(char *cmd) { + sd_limit = atoi(cmd); + sd_count = 0; +} + +static void elim(char *cmd) { + se_limit = atoi(cmd); + se_count = 0; +} + +static void lvals(char *cmd) { + sd_list ^= 1; + printf("Listing node values is now %s.\n", + sd_list ? "on" : "off"); +} + +static void reset(char *cmd) { + int ret; + + ret = ngSpice_Reset_handle(); + if (ret) { + fprintf(stderr, "Reset error %d\n", ret); + return; + } + register_cbs(); + sd_count = se_count = sq_count = 10; + sr_last_time = -1; + sr_target_time = -1; + sim_time = 0; +} + +static void sask(char *cmd) { + pvector_info vp; + + sq_ask ^= 1; + printf("Prompting for V/ISRC values is now %s.\n", + sq_ask ? "on" : "off"); +} + +static void slim(char *cmd) { + sq_limit = atoi(cmd); + sq_count = 0; +} + +static void sramp(char *cmd) { + double v[3]; + int i; + + for (i = 0; i < 3; ++i) { + if (!*cmd) + break; + v[i] = strtod(cmd, NULL); + cmd = skip(tail(cmd)); + } + if (sr_last_time < 0.0) + sr_last_time = sim_time; + switch (i) { + case 0: + break; + case 1: + sr_last_val = sr_target_val = v[0]; + break; + case 2: + sr_target_time = sr_last_time + v[0]; + sr_target_val = v[1]; + break; + default: + sr_last_val = sr_target_val = v[0]; + sr_target_time = sr_last_time + v[1]; + sr_target_val = v[2]; + break; + } + if (sr_target_time < 0.0) + sr_target_time = sim_time + 1.0; +} + +static void vec(char *cmd) { + pvector_info vp; + + vp = ngSpice_GVI_handle(cmd); + if (!vp) + return; + printf("Vector %s: length %d type %d flags %0hx\n", + vp->v_name, vp->v_length, vp->v_type, vp->v_flags); +} + +struct raw_cb_ctx { + int type; + char name[1]; +}; + +static void xnode(char *cmd) { + struct raw_cb_ctx *ctx; + char *end, c; + int ret; + + while (*cmd) { + /* Memory leak here. */ + + end = tail(cmd); + ctx = (struct raw_cb_ctx *)malloc(sizeof *ctx + (end - cmd)); + c = *end; + *end = '\0'; + strcpy(ctx->name, cmd); + ret = ngSpice_Raw_Evt_handle(cmd, ng_rawevt, ctx); + if (ret >= 0) { + ctx->type = ret; + } else { + free(ctx); + fprintf(stderr, "Node name not recognised\n"); + } + *end = c; + cmd = skip(end); + } +} + +#define E(name) { #name, name } + +static void local(char *cmd) +{ + static const struct { + const char *cmd; + void (*fn)(char *); + } table[] = { E(help), // First, so that just "/" works. + E(aevt), E(avec), E(aplot), E(bgr), E(cplot), + E(dlim), E(elim), E(lvals), E(reset), E(sask), E(slim), + E(sramp), E(vec), E(xnode), + { NULL, NULL }}; + char *end; + int i, len; + + end = tail(cmd); + len = end - cmd; + for (i = 0; table[i].cmd; ++i) { + if (!strncmp(cmd, table[i].cmd, len)) { + table[i].fn(skip(end)); + return; + } + } + fprintf(stderr, "No such local command\n"); +} + +int main(int argc, char **argv) +{ + char *libpath = NULL; + int ret, i, do_auto = 0;; + + for (i = 1; i < argc; ++i) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'a': + do_auto = 1; + break; + case 'l': + libpath = argv[i + 1]; + break; + default: + fprintf(stderr, "Unknown option: %s\n", argv[i]); + return 2; + } + } + } + + if (!libpath) + libpath = LOAD_STRING; + ngdllhandle = dlopen(libpath, RTLD_GLOBAL | RTLD_NOW); + if (ngdllhandle) { + printf("Ngspice library loaded.\n"); + } else { + fprintf(stderr, "Ngspice library not loaded!\n %s\n", dlerror()); + return 1; + } + + ngSpice_AllEvtNodes_handle = getsym("ngSpice_AllEvtNodes"); + ngSpice_Command_handle = getsym("ngSpice_Command"); + ngSpice_CurPlot_handle = getsym("ngSpice_CurPlot"); + ngSpice_AllPlots_handle = getsym("ngSpice_AllPlots"); + ngSpice_AllVecs_handle = getsym("ngSpice_AllVecs"); + ngSpice_Decode_Evt_handle = getsym("ngSpice_Decode_Evt"); + ngSpice_GVI_handle = getsym("ngGet_Vec_Info"); + ngSpice_Init_handle = getsym("ngSpice_Init"); + ngSpice_Init_Evt_handle = getsym("ngSpice_Init_Evt"); + ngSpice_Init_Sync_handle = getsym("ngSpice_Init_Sync"); + ngSpice_Raw_Evt_handle = getsym("ngSpice_Raw_Evt"); + ngSpice_Reset_handle = getsym("ngSpice_Reset"); + ngSpice_running_handle = getsym("ngSpice_running"); + register_cbs(); + + if (do_auto) + return auto_test(); + + /* Interactive. */ + + printf("Enter \"/h\" for local command descriptions.\n\n"); + + for (;;) { + /* get command from stdin */ + + if (getLine("Command: ", comd, sizeof(comd))) + return 0; // EOF + + /* Check for a locally-executed command. */ + + if (comd[0] == '/') { + local(comd + 1); + continue; + } + + /* return upon 'exit' */ + + if (cieq("exit", comd)) + break; + + /* If command 'bg_run' is given, ngSpice_Command_handle() will return immediately. + To guarantee that the primary thread here waits until the run is finished, we + may set no_bg to 0 already here. Risk: if starting the simulation fails, we never + may leave the waiting loop. As an alternative callback function ng_thread_runs() + will set no_bg to 0. This has to happen within the first 200ms waiting time. */ + if (cieq("bg_run", comd)) + no_bg = false; + + ret = ngSpice_Command_handle(comd); + if (ret) + fprintf(stderr, "Ngspice command execution error %d\n", ret); + + /* wait until simulation finishes */ + + for (;;) { +#if defined(__MINGW32__) || defined(__CYGWIN__) || defined(_MSC_VER) + Sleep(200); +#else + usleep(200000); +#endif + /* after 200ms the callback function ng_thread_runs() should have + set no_bg to 0, otherwise we would not wait for the end of the + background thread.*/ + if (no_bg) + break; + } + } + ret = ngSpice_Reset_handle(); + return 0; +} + + +/* Callback function called from bg thread in ngspice to transfer + any string created by printf or puts. Output to stdout in ngspice is + preceded by token stdout, same with stderr.*/ +static int +ng_getchar(char* outputreturn, int ident, void* userdata) +{ + printf("%s\n", outputreturn); + return 0; +} + +/* Callback function called from bg thread in ngspice to transfer + simulation status (type and progress in percent). */ +static int +ng_getstat(char* outputreturn, int ident, void* userdata) +{ + printf("Getstat callback: %s\n", outputreturn); + return 0; +} + +/* Callback function called from ngspice upon starting (returns true) or + leaving (returns false) the bg thread. */ +static int +ng_thread_runs(bool noruns, int ident, void* userdata) +{ + no_bg = noruns; + if (noruns) + printf("\nbg not running\n"); + else + printf("bg running\n\n"); + + return 0; +} + +/* Callback function called from bg thread in ngspice if fcn controlled_exit() + is hit. Do not exit, but unload ngspice. */ +static int +ng_exit(int exitstatus, bool immediate, bool quitexit, int ident, void* userdata) +{ + printf("Exit callback status %d immediate %d quit %d\n", + exitstatus, immediate, quitexit); + return exitstatus; +} + +/* Callback function called from bg thread in ngspice once per + * accepted data point: Sendata callbick. + */ + +static int +ng_data(pvecvaluesall vdata, int numvecs, int ident, void* userdata) +{ + if (sd_limit > sd_count) { + ++sd_count; + if (numvecs > 0) { + printf("New data %d (%d) vectors, first is %s\n", + numvecs, vdata->veccount, vdata->vecsa[0]->name); + if (sd_list) { + int i; + + for (i = 0; i < vdata->veccount; ++i) { + if (vdata->vecsa[i]->is_complex) { + printf("%s: (%g, %g)\n", vdata->vecsa[i]->name, + vdata->vecsa[i]->creal, vdata->vecsa[i]->cimag); + } else { + printf("%s: %g\n", + vdata->vecsa[i]->name, vdata->vecsa[i]->creal); + } + } + } + } else { + printf("New data callback, no data!\n"); + } + } + return 0; +} + +/* Callback function called from bg thread in ngspice once upon intialization + * of the simulation vectors. + */ + +static int +ng_initdata(pvecinfoall intdata, int ident, void* userdata) +{ + int i; + int vn = intdata->veccount; + + printf("Init data callback:\n"); + for (i = 0; i < vn; i++) + printf(" Vector: %s\n", intdata->vecs[i]->vecname); + return 0; +} + +static int ng_initevtdata(int idx, int max_idx, char *node, char *type, + int ident, void* userdata) +{ + printf("Evt init: node %s type %s\n", node, type); + return 0; +} + +static int ng_evtdata(int idx, double time, double pval, char *sval, + void *sp, int sz, int sim_node, + int ident, void* userdata) +{ + if (se_limit > se_count) { + ++se_count; + printf("Evt val: node %d val %s/%g at time %g\n", + idx, sval, pval, time); + } + return 0; +} + +static int ng_rawevt(double time, void *valp, void *userData, int last) +{ + struct raw_cb_ctx *ctx = (struct raw_cb_ctx *)userData; + const char *printstr, *typestr; + double plotval; + + if (se_limit > se_count) { + ++se_count; + ngSpice_Decode_Evt_handle(valp, ctx->type, &plotval, &printstr); + ngSpice_Decode_Evt_handle(NULL, ctx->type, NULL, &typestr); + printf("Raw event: node %s type %s val %s/%g at time %g%s\n", + ctx->name, typestr, printstr, plotval, time, + last ? " is last." : ""); + return 0; + } else { + free(ctx); + return 1; + } +} + +/* EXTERNAL source callback. */ + +static int ng_srcdata(double *vp, double time, char *source, int id, void *udp) +{ + if (sq_limit > sq_count) { + ++sq_count; + printf("V or ISRC request: source %s at time %g\n", source, time); + if (sq_ask) { + getLine("Value: ", comd, sizeof(comd)); + if (!strncmp("/s", comd, 2)) { + /* Allow "/sask" as respone. */ + + sq_ask = 0; + } else { + sr_last_val = *vp = strtod(comd, NULL); + sr_last_time = time; + } + return 0; + } + } + + if (sr_last_time >= 0.0) { + /* Provide a value. */ + + if (sr_target_time >= 0.0) { + if (time < sr_target_time) { + sr_last_val += (sr_target_val - sr_last_val) * + (time - sr_last_time) / (sr_target_time - sr_last_time); + } else { + sr_last_val = sr_target_val; + } + } + *vp = sr_last_val; + sr_last_time = time; + } + return 0; +} + +static int ng_syncdata(double time, double *deltap, double old_delta, + int redo, int loc, int id, void *udp) +{ + if (sd_limit > sd_count) { + ++sd_count; + printf("Sync data redo %d delta %g (old %g) location %d at %g\n", + redo, *deltap, old_delta, loc, time); + } + sim_time = time; + return 0; +} + +/* Unify LINUX and Windows dynamic library handling: + Add functions dlopen, dlsym, dlerror, dlclose to Windows by + tranlating to Windows API functions. +*/ +#if defined(__MINGW32__) || defined(__CYGWIN__) || defined(_MSC_VER) + +void *dlopen(const char *name,int type) +{ + return LoadLibrary((LPCSTR)name); +} + +funptr_t dlsym(void *hDll, const char *funcname) +{ + return GetProcAddress(hDll, funcname); +} + +char *dlerror(void) +{ + LPVOID lpMsgBuf; + char * testerr; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + testerr = (char*)lpMsgBuf; + strcpy(errstr,lpMsgBuf); + LocalFree(lpMsgBuf); + if (ciprefix("Der Vorgang wurde erfolgreich beendet.", errstr)) + return NULL; + else + return errstr; +} + +int dlclose (void *lhandle) +{ + return (int)FreeLibrary(lhandle); +} +#endif + +/* Case insensitive str eq. + Like strcasecmp( ) XXX */ +static int +cieq(register char *p, register char *s) +{ + while (*p) { + if ((isupper(*p) ? tolower(*p) : *p) != + (isupper(*s) ? tolower(*s) : *s)) + return(false); + p++; + s++; + } + return (*s ? false : true); +} + +/* Case insensitive prefix. */ +static int +ciprefix(const char *p, const char *s) +{ + while (*p) { + if ((isupper(*p) ? tolower(*p) : *p) != + (isupper(*s) ? tolower(*s) : *s)) + return(false); + p++; + s++; + } + return (true); +} + +/* read a line from console input + source: + https://stackoverflow.com/questions/4023895/how-to-read-string-entered-by-user-in-c + */ +#define OK 0 +#define NO_INPUT 1 + +static int +getLine(char *prmpt, char *buff, size_t sz) +{ + int ch, len, extra; + + // Get line with buffer overrun protection. + + for (;;) { + if (prmpt != NULL) { + printf("%s", prmpt); + fflush(stdout); + } + if (fgets(buff, sz, stdin) == NULL) + return NO_INPUT; + + // If it was too long, there'll be no newline. In that case, we flush + // to end of line so that excess doesn't affect the next call. + + len = strlen(buff); + if (buff[len - 1] != '\n') { + extra = 0; + while (((ch = getchar()) != '\n') && (ch != EOF)) + extra = 1; + if (extra) { + fprintf(stderr, + "Line longer than %zd characters was ignored.\n", + sz - 1); + continue; + } + } + + // Otherwise remove newline and give string back to caller. + + buff[len - 1] = '\0'; + return OK; + } +} diff --git a/examples/shared/t.cir b/examples/shared/t.cir new file mode 100644 index 000000000..73929a1c1 --- /dev/null +++ b/examples/shared/t.cir @@ -0,0 +1,34 @@ +Simple test circuit for shx program. +* A typical command sequence in shx: +* t1.cir +* /lvals +* /aevt +* /elim 20 +* /dlim 20 +* /slim 20 +* /xnode clk div +* run + +* d_osc controlled by EXTERNAL source + +vext ctl 0 0 external +aosc ctl clk osc +.model osc d_osc cntl_array = [0 20] freq_array = [ 100k 1meg ] + +# Divide by three so there will be multiple transitions, reported by /aevt +# within each time-step. + +adiv clk div div +.model div d_fdiv div_factor = 3 + +* Add a resistor to convert to analogue and force breakpoints. + +r div 0 100 + +* Use .tran so that /xnode commands can be given after loading. +* Set maximum time step above div cycle time, so breakpoints control +* time steps. Long run time to overcome the 50 steps minimum. + +.tran 4u 201u + +.end diff --git a/examples/shared/t.shx b/examples/shared/t.shx new file mode 100755 index 000000000..a16d32131 --- /dev/null +++ b/examples/shared/t.shx @@ -0,0 +1,23 @@ +# Input file running shx with t.cir +# An externally controlled source is set to 0.5V from prompted input, +# then prompting is disabled and the source value is ramped after a stop +# +# The result may be plotted with +# load t.raw +# plot v(ctl) v(div) +# +t.cir +/lvals +/aevt +/elim 20 +/dlim 20 +/slim 20 +/xnode clk div +/sask +stop when time = 50u +run +0.5 +/sask +/sram 1 0.0001 20 +resume +write t.raw diff --git a/examples/xspice/pll/pll-digital-iplot.cir b/examples/xspice/pll/pll-digital-iplot.cir new file mode 100644 index 000000000..6198dbe90 --- /dev/null +++ b/examples/xspice/pll/pll-digital-iplot.cir @@ -0,0 +1,20 @@ +* pll circuit using xspice code models: version with iplot including +* digital nodes. + +.include shared-pll-xspice.cir + +* An additional node to scale the analog signal. + +bdisplay controlX4 0 v=v(cont)*4 + +.control +save cont controlX4 s1 s2 u1n d1 v.xlf.vdd#branch; to save memory +iplot -o controlx4 d_d+4.5 d_u +tran 0.1n $&simtime uic +rusage +plot cont s1 s2+1.2 u1n+2.4 d1+3.6 xlimit 4u 5u +plot v.xlf.vdd#branch xlimit 4u 5u ylimit -8m 2m +*plot cont +.endc + +.end diff --git a/examples/xspice/pll/pll-xspice.cir b/examples/xspice/pll/pll-xspice.cir index a1df101f1..3f66f1b11 100644 --- a/examples/xspice/pll/pll-xspice.cir +++ b/examples/xspice/pll/pll-xspice.cir @@ -1,66 +1,8 @@ -* pll circuit using xspice code models +* pll circuit using xspice code models: version with analog-only iplot * output frequency 400 MHz * locked to a 1 or 10 MHz reference -.param vcc=3.3 -.param divisor=40 -.param fref=10e6 -.csparam simtime=25u - -.global d_d0 d_d1 - -vdd dd 0 dc 'vcc' -*vco cont 0 dc 1.9 - -*PULSE(V1 V2 TD TR TF PW PER) -* reference frequency selected by param fref -* PULSE(V1 V2 TD TR TF PW PER) -vref ref 0 dc 0 pulse(0 'vcc' 10n 1n 1n '1/fref/2' '1/fref') -abridgeref [ref] [d_ref] adc_vbuf -.model adc_vbuf adc_bridge(in_low = 0.5 in_high = 0.5) - -*digital zero -vzero z 0 dc 0 -abridgev3 [z] [d_d0] adc_vbuf -.model adc_vbuf adc_bridge(in_low = 'vcc*0.5' in_high = 'vcc*0.5') -*digital one -ainv1 d_d0 d_d1 invd1 -.model invd1 d_inverter(rise_delay = 1e-10 fall_delay = 1e-10) - -* vco -* buf: analog out -* d_digout: digital out -* cont: analog control voltage -* dd: analog supply voltage -*.include vco_sub.cir -*xvco buf d_digout cont dd ro_vco -.include vco_sub_new.cir -xvco buf d_digout cont dd d_osc_vco - -* digital divider -adiv1 d_digout d_divout divider -.model divider d_fdiv(div_factor = 'divisor' high_cycles = 'divisor/2' -+ i_count = 4 rise_delay = 1e-10 -+ fall_delay = 1e-10) - -* frequency phase detector -.include f-p-det-d-sub.cir -Xfpdet d_divout d_ref d_U d_Un d_D d_Dn f-p-det - -* loop filters -*2nd or 3rd order, transistors as switches -.include loop-filter-2.cir -Xlf d_Un d_D cont loopf -* 2nd order, Exxxx voltage controlled current sources as 'switches' -* loop filter current sources as charge pump -*.include loop-filter.cir -*Xlf d_U d_D cont loopfe - -* d to a for plotting -abridge-w1 [d_divout d_ref d_Un d_D] [s1 s2 u1n d1] dac1 ; change to d_u or d_Un -.model dac1 dac_bridge(out_low = 0 out_high = 1 out_undef = 0.5 -+ input_load = 5.0e-12 t_rise = 1e-10 -+ t_fall = 1e-10) +.include shared-pll-xspice.cir .control save cont s1 s2 u1n d1 v.xlf.vdd#branch; to save memory @@ -69,76 +11,5 @@ tran 0.1n $&simtime uic rusage plot cont s1 s2+1.2 u1n+2.4 d1+3.6 xlimit 4u 5u plot v.xlf.vdd#branch xlimit 4u 5u ylimit -8m 2m -*plot cont .endc - -*model = bsim3v3 -*Berkeley Spice Compatibility -* Lmin= .35 Lmax= 20 Wmin= .6 Wmax= 20 -.model N1 NMOS -*+version = 3.2.4 -+version = 3.3.0 -+Level= 8 -+Tnom=27.0 -+Nch= 2.498E+17 Tox=9E-09 Xj=1.00000E-07 -+Lint=9.36e-8 Wint=1.47e-7 -+Vth0= .6322 K1= .756 K2= -3.83e-2 K3= -2.612 -+Dvt0= 2.812 Dvt1= 0.462 Dvt2=-9.17e-2 -+Nlx= 3.52291E-08 W0= 1.163e-6 -+K3b= 2.233 -+Vsat= 86301.58 Ua= 6.47e-9 Ub= 4.23e-18 Uc=-4.706281E-11 -+Rdsw= 650 U0= 388.3203 wr=1 -+A0= .3496967 Ags=.1 B0=0.546 B1= 1 -+ Dwg = -6.0E-09 Dwb = -3.56E-09 Prwb = -.213 -+Keta=-3.605872E-02 A1= 2.778747E-02 A2= .9 -+Voff=-6.735529E-02 NFactor= 1.139926 Cit= 1.622527E-04 -+Cdsc=-2.147181E-05 -+Cdscb= 0 Dvt0w = 0 Dvt1w = 0 Dvt2w = 0 -+ Cdscd = 0 Prwg = 0 -+Eta0= 1.0281729E-02 Etab=-5.042203E-03 -+Dsub= .31871233 -+Pclm= 1.114846 Pdiblc1= 2.45357E-03 Pdiblc2= 6.406289E-03 -+Drout= .31871233 Pscbe1= 5000000 Pscbe2= 5E-09 Pdiblcb = -.234 -+Pvag= 0 delta=0.01 -+ Wl = 0 Ww = -1.420242E-09 Wwl = 0 -+ Wln = 0 Wwn = .2613948 Ll = 1.300902E-10 -+ Lw = 0 Lwl = 0 Lln = .316394 -+ Lwn = 0 -+kt1=-.3 kt2=-.051 -+At= 22400 -+Ute=-1.48 -+Ua1= 3.31E-10 Ub1= 2.61E-19 Uc1= -3.42e-10 -+Kt1l=0 Prt=764.3 - -.model P1 PMOS -*+version = 3.2.4 -+version = 3.3.0 -+Level= 8 -+Tnom=27.0 -+Nch= 3.533024E+17 Tox=9E-09 Xj=1.00000E-07 -+Lint=6.23e-8 Wint=1.22e-7 -+Vth0=-.6732829 K1= .8362093 K2=-8.606622E-02 K3= 1.82 -+Dvt0= 1.903801 Dvt1= .5333922 Dvt2=-.1862677 -+Nlx= 1.28e-8 W0= 2.1e-6 -+K3b= -0.24 Prwg=-0.001 Prwb=-0.323 -+Vsat= 103503.2 Ua= 1.39995E-09 Ub= 1.e-19 Uc=-2.73e-11 -+ Rdsw= 460 U0= 138.7609 -+A0= .4716551 Ags=0.12 -+Keta=-1.871516E-03 A1= .3417965 A2= 0.83 -+Voff=-.074182 NFactor= 1.54389 Cit=-1.015667E-03 -+Cdsc= 8.937517E-04 -+Cdscb= 1.45e-4 Cdscd=1.04e-4 -+ Dvt0w=0.232 Dvt1w=4.5e6 Dvt2w=-0.0023 -+Eta0= 6.024776E-02 Etab=-4.64593E-03 -+Dsub= .23222404 -+Pclm= .989 Pdiblc1= 2.07418E-02 Pdiblc2= 1.33813E-3 -+Drout= .3222404 Pscbe1= 118000 Pscbe2= 1E-09 -+Pvag= 0 -+kt1= -0.25 kt2= -0.032 prt=64.5 -+At= 33000 -+Ute= -1.5 -+Ua1= 4.312e-9 Ub1= 6.65e-19 Uc1= 0 -+Kt1l=0 - - .end diff --git a/examples/xspice/pll/shared-pll-xspice.cir b/examples/xspice/pll/shared-pll-xspice.cir new file mode 100644 index 000000000..b3ea64049 --- /dev/null +++ b/examples/xspice/pll/shared-pll-xspice.cir @@ -0,0 +1,133 @@ +* pll circuit using xspice code models: shared by pll_xspice.cir and +* pll_digital_iplot.cir +* output frequency 400 MHz +* locked to a 1 or 10 MHz reference + +.param vcc=3.3 +.param divisor=40 +.param fref=10e6 +.csparam simtime=25u + +.global d_d0 d_d1 + +vdd dd 0 dc 'vcc' +*vco cont 0 dc 1.9 + +*PULSE(V1 V2 TD TR TF PW PER) +* reference frequency selected by param fref +* PULSE(V1 V2 TD TR TF PW PER) +vref ref 0 dc 0 pulse(0 'vcc' 10n 1n 1n '1/fref/2' '1/fref') +abridgeref [ref] [d_ref] adc_vbuf +.model adc_vbuf adc_bridge(in_low = 0.5 in_high = 0.5) + +*digital zero +vzero z 0 dc 0 +abridgev3 [z] [d_d0] adc_vbuf +.model adc_vbuf adc_bridge(in_low = 'vcc*0.5' in_high = 'vcc*0.5') +*digital one +ainv1 d_d0 d_d1 invd1 +.model invd1 d_inverter(rise_delay = 1e-10 fall_delay = 1e-10) + +* vco +* buf: analog out +* d_digout: digital out +* cont: analog control voltage +* dd: analog supply voltage +*.include vco_sub.cir +*xvco buf d_digout cont dd ro_vco +.include vco_sub_new.cir +xvco buf d_digout cont dd d_osc_vco + +* digital divider +adiv1 d_digout d_divout divider +.model divider d_fdiv(div_factor = 'divisor' high_cycles = 'divisor/2' ++ i_count = 4 rise_delay = 1e-10 ++ fall_delay = 1e-10) + +* frequency phase detector +.include f-p-det-d-sub.cir +Xfpdet d_divout d_ref d_U d_Un d_D d_Dn f-p-det + +* loop filters +*2nd or 3rd order, transistors as switches +.include loop-filter-2.cir +Xlf d_Un d_D cont loopf +* 2nd order, Exxxx voltage controlled current sources as 'switches' +* loop filter current sources as charge pump +*.include loop-filter.cir +*Xlf d_U d_D cont loopfe + +* d to a for plotting +abridge-w1 [d_divout d_ref d_Un d_D] [s1 s2 u1n d1] dac1 ; change to d_u or d_Un +.model dac1 dac_bridge(out_low = 0 out_high = 1 out_undef = 0.5 ++ input_load = 5.0e-12 t_rise = 1e-10 ++ t_fall = 1e-10) + +*model = bsim3v3 +*Berkeley Spice Compatibility +* Lmin= .35 Lmax= 20 Wmin= .6 Wmax= 20 +.model N1 NMOS +*+version = 3.2.4 ++version = 3.3.0 ++Level= 8 ++Tnom=27.0 ++Nch= 2.498E+17 Tox=9E-09 Xj=1.00000E-07 ++Lint=9.36e-8 Wint=1.47e-7 ++Vth0= .6322 K1= .756 K2= -3.83e-2 K3= -2.612 ++Dvt0= 2.812 Dvt1= 0.462 Dvt2=-9.17e-2 ++Nlx= 3.52291E-08 W0= 1.163e-6 ++K3b= 2.233 ++Vsat= 86301.58 Ua= 6.47e-9 Ub= 4.23e-18 Uc=-4.706281E-11 ++Rdsw= 650 U0= 388.3203 wr=1 ++A0= .3496967 Ags=.1 B0=0.546 B1= 1 ++ Dwg = -6.0E-09 Dwb = -3.56E-09 Prwb = -.213 ++Keta=-3.605872E-02 A1= 2.778747E-02 A2= .9 ++Voff=-6.735529E-02 NFactor= 1.139926 Cit= 1.622527E-04 ++Cdsc=-2.147181E-05 ++Cdscb= 0 Dvt0w = 0 Dvt1w = 0 Dvt2w = 0 ++ Cdscd = 0 Prwg = 0 ++Eta0= 1.0281729E-02 Etab=-5.042203E-03 ++Dsub= .31871233 ++Pclm= 1.114846 Pdiblc1= 2.45357E-03 Pdiblc2= 6.406289E-03 ++Drout= .31871233 Pscbe1= 5000000 Pscbe2= 5E-09 Pdiblcb = -.234 ++Pvag= 0 delta=0.01 ++ Wl = 0 Ww = -1.420242E-09 Wwl = 0 ++ Wln = 0 Wwn = .2613948 Ll = 1.300902E-10 ++ Lw = 0 Lwl = 0 Lln = .316394 ++ Lwn = 0 ++kt1=-.3 kt2=-.051 ++At= 22400 ++Ute=-1.48 ++Ua1= 3.31E-10 Ub1= 2.61E-19 Uc1= -3.42e-10 ++Kt1l=0 Prt=764.3 + +.model P1 PMOS +*+version = 3.2.4 ++version = 3.3.0 ++Level= 8 ++Tnom=27.0 ++Nch= 3.533024E+17 Tox=9E-09 Xj=1.00000E-07 ++Lint=6.23e-8 Wint=1.22e-7 ++Vth0=-.6732829 K1= .8362093 K2=-8.606622E-02 K3= 1.82 ++Dvt0= 1.903801 Dvt1= .5333922 Dvt2=-.1862677 ++Nlx= 1.28e-8 W0= 2.1e-6 ++K3b= -0.24 Prwg=-0.001 Prwb=-0.323 ++Vsat= 103503.2 Ua= 1.39995E-09 Ub= 1.e-19 Uc=-2.73e-11 ++ Rdsw= 460 U0= 138.7609 ++A0= .4716551 Ags=0.12 ++Keta=-1.871516E-03 A1= .3417965 A2= 0.83 ++Voff=-.074182 NFactor= 1.54389 Cit=-1.015667E-03 ++Cdsc= 8.937517E-04 ++Cdscb= 1.45e-4 Cdscd=1.04e-4 ++ Dvt0w=0.232 Dvt1w=4.5e6 Dvt2w=-0.0023 ++Eta0= 6.024776E-02 Etab=-4.64593E-03 ++Dsub= .23222404 ++Pclm= .989 Pdiblc1= 2.07418E-02 Pdiblc2= 1.33813E-3 ++Drout= .3222404 Pscbe1= 118000 Pscbe2= 1E-09 ++Pvag= 0 ++kt1= -0.25 kt2= -0.032 prt=64.5 ++At= 33000 ++Ute= -1.5 ++Ua1= 4.312e-9 Ub1= 6.65e-19 Uc1= 0 ++Kt1l=0 + diff --git a/src/frontend/breakp.c b/src/frontend/breakp.c index 83eab7771..a02fb40cd 100644 --- a/src/frontend/breakp.c +++ b/src/frontend/breakp.c @@ -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); diff --git a/src/frontend/commands.c b/src/frontend/commands.c index 41671950c..3de1ba6fb 100644 --- a/src/frontend/commands.c +++ b/src/frontend/commands.c @@ -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, diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index c60a16d6c..c6d678386 100644 --- a/src/frontend/inpcom.c +++ b/src/frontend/inpcom.c @@ -198,6 +198,10 @@ static void utf8_syntax_check(struct card *deck); int add_to_sourcepath(const char* filepath, const char* path); +#if defined(_WIN32) +static char* get_windows_canonical_path(const char* input_path); +#endif + struct inp_read_t { struct card *cc; int line_number; @@ -1969,19 +1973,20 @@ FILE *inp_pathopen(const char *name, const char *mode) if the file isn't in . and it isn't an abs path name. *-------------------------------------------------------------------------*/ -char *inp_pathresolve(const char *name) +char *inp_pathresolve(const char *cname) { struct variable *v; struct stat st; + char* name; #if defined(_WIN32) /* If variable 'mingwpath' is set: convert mingw /d/... to d:/... */ if (cp_getvar("mingwpath", CP_BOOL, NULL, 0) && - name[0] == DIR_TERM_LINUX && isalpha_c(name[1]) && - name[2] == DIR_TERM_LINUX) { + cname[0] == DIR_TERM_LINUX && isalpha_c(cname[1]) && + cname[2] == DIR_TERM_LINUX) { DS_CREATE(ds, 100); - if (ds_cat_str(&ds, name) != 0) { + if (ds_cat_str(&ds, cname) != 0) { fprintf(stderr, "Error: Unable to copy string while resolving path"); controlled_exit(EXIT_FAILURE); } @@ -1993,27 +1998,33 @@ char *inp_pathresolve(const char *name) return resolved_path; } + /* Try to overcome MAX_PATH path length limit by removing '/..' */ + name = get_windows_canonical_path(cname); +#else + name = copy(cname); #endif /* just try it */ if (stat(name, &st) == 0) - return copy(name); - + return name; + #if !defined(EXT_ASC) && (defined(__MINGW32__) || defined(_MSC_VER)) wchar_t wname[BSIZE_SP]; if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, 2 * (int)strlen(name) + 1) == 0) { fprintf(stderr, "UTF-8 to UTF-16 conversion failed with 0x%x\n", GetLastError()); fprintf(stderr, "%s could not be converted\n", name); + tfree(name); return NULL; } if (_waccess(wname, 0) == 0) - return copy(name); -#endif + return name; +#endif /* fail if this was an absolute filename or if there is no sourcepath var */ if (is_absolute_pathname(name) || !cp_getvar("sourcepath", CP_LIST, &v, 0)) { + tfree(name); return (char *) NULL; } @@ -2040,12 +2051,14 @@ char *inp_pathresolve(const char *name) fprintf(stderr, "ERROR: enumeration value `CP_BOOL' or `CP_LIST' " "not handled in inp_pathresolve\nAborting...\n"); + tfree(name); controlled_exit(EXIT_FAILURE); } if (rc_ds != 0) { /* unable to build string */ (void) fprintf(cp_err, "Error: Unable to build path name in inp_pathresolve"); + tfree(name); controlled_exit(EXIT_FAILURE); } @@ -2056,6 +2069,7 @@ char *inp_pathresolve(const char *name) char * const buf_cpy = dup_string( buf, ds_get_length(&ds)); ds_free(&ds); + tfree(name); return buf_cpy; } /* Else contiue with next attempt */ @@ -2063,7 +2077,7 @@ char *inp_pathresolve(const char *name) } /* end of loop over linked variables */ ds_free(&ds); } /* end of block trying to find a valid name */ - + tfree(name); return (char *) NULL; } /* end of function inp_pathresolve */ @@ -8988,7 +9002,10 @@ static void inp_check_syntax(struct card *deck) if (check_subs != 0) { fprintf(cp_err, "\nError: Mismatch of .subckt ... .ends statements!\n"); - fprintf(stderr, " in file %s\n", bugcard->linesource); + if (eq("circbyline", bugcard->linesource)) + fprintf(stderr, "in the netlist received from the calling program\n"); + else + fprintf(stderr, " in file %s\n", bugcard->linesource); fprintf(cp_err, " This will cause subsequent errors.\n\n"); if (ends > 0) fprintf(cp_err, "Check .ends in line number %d\n", ends); @@ -9719,8 +9736,8 @@ int add_to_sourcepath(const char* filepath, const char* path) else return 1; - startwl = newwl = wl_from_string("sourcepath = ( "); - endwl = wl_from_string(" )"); + startwl = newwl = wl_from_string("sourcepath=("); + endwl = wl_from_string(")"); /* add fpath to 'sourcepath' list variable */ if (cp_getvar("sourcepath", CP_LIST, NULL, 0)) { @@ -9746,3 +9763,134 @@ int add_to_sourcepath(const char* filepath, const char* path) tfree(fpath); return 0; } + + +#if defined(_WIN32) + + +/** + * @brief Resolves a Windows path to its canonical, absolute form using GetFullPathNameW. + * + * This function takes a path string (assumed to be UTF-8), converts it to + * UTF-16, calls the Windows API GetFullPathNameW to resolve '..' and '.' + * components and make the path absolute, and then converts the result back + * to a newly allocated UTF-8 string. + * + * It handles potential failures during conversion or path resolution. + * It does NOT automatically add the '\\?\' prefix for long paths, but + * GetFullPathNameW can produce paths longer than MAX_PATH. The caller + * might need to add the prefix separately if using the result in APIs + * that require it for long path support. + * + * @param input_path The input path string (UTF-8 encoded). Can be relative or + * absolute, may contain '.' or '..'. + * @return char* A newly allocated UTF-8 string containing the canonical absolute + * path, or NULL on failure. The caller is responsible for + * calling free() on the returned string. On failure, errno is + * set to indicate the error (e.g., ENOMEM, EINVAL, ENOENT). + */ +char* get_windows_canonical_path(const char* input_path) { + wchar_t* wPathInput = NULL; + wchar_t* wPathOutput = NULL; + char* utf8PathOutput = NULL; + DWORD inputLenW = 0; + DWORD outputLenW = 0; + DWORD resultLenW = 0; + int inputLenMB = 0; + int outputLenMB = 0; + int original_errno = errno; + + if (input_path == NULL) { + errno = EINVAL; + return NULL; + } + + inputLenMB = (int)strlen(input_path); + + if (inputLenMB == 0) { + inputLenW = 1; + } + else { + inputLenW = MultiByteToWideChar(CP_UTF8, 0, input_path, inputLenMB, NULL, 0); + if (inputLenW == 0) { + errno = EINVAL; + return NULL; + } + inputLenW++; + } + + wPathInput = TMALLOC(wchar_t, inputLenW * sizeof(wchar_t)); + if (!wPathInput) { + errno = ENOMEM; + return NULL; + } + + if (MultiByteToWideChar(CP_UTF8, 0, input_path, inputLenMB + 1, wPathInput, inputLenW) == 0) { + tfree(wPathInput); + errno = EINVAL; + return NULL; + } + + errno = original_errno; + outputLenW = GetFullPathNameW(wPathInput, 0, NULL, NULL); + if (outputLenW == 0) { + DWORD dwError = GetLastError(); + if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (dwError == ERROR_ACCESS_DENIED) + errno = EACCES; + else + errno = EINVAL; + tfree(wPathInput); + return NULL; + } + + wPathOutput = (wchar_t*)malloc(outputLenW * sizeof(wchar_t)); + if (!wPathOutput) { + tfree(wPathInput); + errno = ENOMEM; + return NULL; + } + + errno = original_errno; + resultLenW = GetFullPathNameW(wPathInput, outputLenW, wPathOutput, NULL); + free(wPathInput); + + if (resultLenW == 0 || resultLenW >= outputLenW) { + DWORD dwError = GetLastError(); + if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (dwError == ERROR_ACCESS_DENIED) + errno = EACCES; + else + errno = EINVAL; + tfree(wPathOutput); + return NULL; + } + + outputLenMB = WideCharToMultiByte(CP_UTF8, 0, wPathOutput, -1, NULL, 0, NULL, NULL); + if (outputLenMB == 0) { + tfree(wPathOutput); + errno = EINVAL; + return NULL; + } + + utf8PathOutput = (char*)malloc(outputLenMB); + if (!utf8PathOutput) { + tfree(wPathOutput); + errno = ENOMEM; + return NULL; + } + + if (WideCharToMultiByte(CP_UTF8, 0, wPathOutput, -1, utf8PathOutput, outputLenMB, NULL, NULL) == 0) { + tfree(wPathOutput); + tfree(utf8PathOutput); + errno = EINVAL; + return NULL; + } + + tfree(wPathOutput); + errno = original_errno; + return utf8PathOutput; +} +#endif diff --git a/src/frontend/outitf.c b/src/frontend/outitf.c index 87329841d..d47e61632 100644 --- a/src/frontend/outitf.c +++ b/src/frontend/outitf.c @@ -146,6 +146,7 @@ beginPlot(JOB *analysisPtr, CKTcircuit *circuitPtr, char *cktName, char *analNam bool saveall = TRUE; bool savealli = FALSE; bool savenosub = FALSE; + bool savenointernals = FALSE; char *an_name; int initmem; /*to resume a run saj @@ -223,6 +224,13 @@ beginPlot(JOB *analysisPtr, CKTcircuit *circuitPtr, char *cktName, char *analNam saves[i].used = 1; continue; } + + if (cieq(saves[i].name, "nointernals")) { + savenointernals = TRUE; + savesused[i] = TRUE; + saves[i].used = 1; + continue; + } #ifdef SHARED_MODULE /* this may happen if shared ngspice*/ if (cieq(saves[i].name, "none")) { @@ -255,7 +263,7 @@ beginPlot(JOB *analysisPtr, CKTcircuit *circuitPtr, char *cktName, char *analNam /* Pass 1. */ - if (numsaves && !saveall && !savenosub) { + if (numsaves && !saveall && !savenosub && !savenointernals) { for (i = 0; i < numsaves; i++) { if (!savesused[i]) { for (j = 0; j < numNames; j++) { @@ -278,15 +286,20 @@ beginPlot(JOB *analysisPtr, CKTcircuit *circuitPtr, char *cktName, char *analNam } else { for (i = 0; i < numNames; i++) if (!refName || !name_eq(dataNames[i], refName)) - /* Save the node as long as it's not an internal device node */ - if (!(savenosub && strchr(dataNames[i], '.')) && /* don't save subckt nodes */ + /* Save the node (with restrictions) */ + /* don't save subckt nodes */ + if (!(savenosub && strchr(dataNames[i], '.')) && + /* no internals at all, but still #branch */ + (!(savenointernals && strstr(dataNames[i], "#")) || strstr(dataNames[i], "#branch")) && + /* created by .probe */ + !strstr(dataNames[i], "probe_int_") && + /* don't save internal device nodes */ !strstr(dataNames[i], "#internal") && !strstr(dataNames[i], "#source") && !strstr(dataNames[i], "#drain") && !strstr(dataNames[i], "#collector") && !strstr(dataNames[i], "#collCX") && !strstr(dataNames[i], "#emitter") && - !strstr(dataNames[i], "probe_int_") && /* created by .probe */ !strstr(dataNames[i], "#base")) { addDataDesc(run, dataNames[i], dataType, i, initmem); @@ -610,6 +623,8 @@ OUTpData(runDesc *plotPtr, IFvalue *refValue, IFvalue *valuePtr) #endif /* interpolated batch mode output to file/plot in transient analysis */ if (interpolated && run->circuit->CKTcurJob->JOBtype == 4) { + /* JOBtype == 4 means Transient Analysis. FIX ME */ + if (run->writeOut) { /* To file */ InterpFileAdd(run, refValue, valuePtr); } @@ -617,10 +632,9 @@ OUTpData(runDesc *plotPtr, IFvalue *refValue, IFvalue *valuePtr) InterpPlotAdd(run, refValue, valuePtr); } return OK; - } + } else if (run->writeOut) { + /* standard batch mode output to file */ - /* standard batch mode output to file */ - else if (run->writeOut) { if (run->pointCount == 1) { fileInit_pass2(run); } @@ -724,7 +738,6 @@ OUTpData(runDesc *plotPtr, IFvalue *refValue, IFvalue *valuePtr) #ifdef TCL_MODULE blt_add(i, valuePtr->v.vec.rVec [run->data[i].outIndex]); #endif - } fileEndPoint(run->fp, run->binary); @@ -1218,12 +1231,9 @@ vlength2delta(int len) return 1024; } - -static void -plotAddRealValue(dataDesc *desc, double value) +void +AddRealValueToVector(struct dvec *v, double value) { - struct dvec *v = desc->vec; - #ifdef SHARED_MODULE if (savenone) /* always save new data to same location */ @@ -1245,6 +1255,11 @@ plotAddRealValue(dataDesc *desc, double value) v->v_dims[0] = v->v_length; /* va, must be updated */ } +static void +plotAddRealValue(dataDesc *desc, double value) +{ + AddRealValueToVector(desc->vec, value); +} static void plotAddComplexValue(dataDesc *desc, IFcomplex value) diff --git a/src/frontend/outitf.h b/src/frontend/outitf.h index 07853146b..efb333119 100644 --- a/src/frontend/outitf.h +++ b/src/frontend/outitf.h @@ -71,5 +71,5 @@ void OUTerrorf(int, const char *fmt, ...) __attribute__ ((format (__printf__, 2 void OUTerrorf(int, const char *fmt, ...); #endif - +void AddRealValueToVector(struct dvec *v, double value); #endif diff --git a/src/frontend/plotting/graf.c b/src/frontend/plotting/graf.c index 8f5404936..3fbfcb237 100644 --- a/src/frontend/plotting/graf.c +++ b/src/frontend/plotting/graf.c @@ -23,16 +23,19 @@ Author: 1988 Jeffrey M. Hsu #include "ngspice/grid.h" #include "ngspice/sim.h" #include "ngspice/stringskip.h" +#include "ngspice/evtproto.h" #include "breakp2.h" #include "display.h" #include "graf.h" #include "graphdb.h" #include "runcoms.h" #include "terminal.h" +#include "outitf.h" static void gr_start_internal(struct dvec *dv, bool copyvec); -static void set(struct plot *plot, struct dbcomm *db, bool value, short mode); +static void setflag(struct plot *plot, struct dbcomm *db, + bool value, short mode); static char *getitright(char *buf, double num); /* for legends, set in gr_start, reset in gr_iplot and gr_init */ @@ -81,7 +84,7 @@ int gr_init(double *xlims, double *ylims, /* The size of the screen. */ const char *commandline, /* For xi_zoomdata() */ int prevgraph) /* plot id, if started from a previous plot*/ { - GRAPH *graph, *pgraph; + GRAPH *graph, *pgraph; wordlist *wl; NG_IGNORE(nplots); @@ -309,7 +312,8 @@ static void drawLine(int x1, int y1, int x2, int y2, struct dvec *dv) { if (LC.dv) { if (LC.dv != dv) { - fprintf(cp_err, "LC: DV changed!\n"); + fprintf(cp_err, "LC: DV changed from %s to %s!\n", + LC.dv->v_name, dv->v_name); LC_flush(); LC.dv = dv; } @@ -540,11 +544,11 @@ static void gr_start_internal(struct dvec *dv, bool copyvec) /* Do something special with poles and zeros. Poles are 'x's, and * zeros are 'o's. */ + if (dv->v_type == SV_POLE) { dv->v_linestyle = 'x'; return; - } - else if (dv->v_type == SV_ZERO) { + } else if (dv->v_type == SV_ZERO) { dv->v_linestyle = 'o'; return; } @@ -553,35 +557,34 @@ static void gr_start_internal(struct dvec *dv, bool copyvec) if (currentgraph->plottype == PLOT_POINT) { if (pointchars[cur.linestyle - 1]) { cur.linestyle++; - } - else { + } else { cur.linestyle = 2; } - } - else if ((cur.linestyle > 0) && - (++cur.linestyle == dispdev->numlinestyles)) { + } else if ((cur.linestyle > 0) && + (++cur.linestyle == dispdev->numlinestyles)) { cur.linestyle = 2; } if ((cur.color > 0) && (++cur.color == dispdev->numcolors)) cur.color = (((currentgraph->grid.gridtype == GRID_SMITH || - currentgraph->grid.gridtype == GRID_SMITHGRID) && - (dispdev->numcolors > 3)) ? 4 : 2); + currentgraph->grid.gridtype == GRID_SMITHGRID) && + (dispdev->numcolors > 3)) ? 4 : 2); if (currentgraph->plottype == PLOT_POINT) { dv->v_linestyle = pointchars[cur.linestyle - 2]; - } - else { + } else { dv->v_linestyle = cur.linestyle; } dv->v_color = cur.color; /* Save the data so we can refresh */ + link = TMALLOC(struct dveclist, 1); link->next = currentgraph->plotdata; /* Either reuse input vector or copy depnding on copyvec */ + if (copyvec) { link->vector = vec_copy(dv); /* vec_copy doesn't set v_color or v_linestyle */ @@ -589,8 +592,7 @@ static void gr_start_internal(struct dvec *dv, bool copyvec) link->vector->v_linestyle = dv->v_linestyle; link->vector->v_flags |= VF_PERMANENT; link->f_own_vector = TRUE; - } - else { + } else { link->vector = dv; link->f_own_vector = FALSE; } @@ -610,6 +612,7 @@ static void gr_start_internal(struct dvec *dv, bool copyvec) } /* Put the legend entry on the screen. */ + if (!currentgraph->nolegend) drawlegend(currentgraph, cur.plotno++, dv); } @@ -846,6 +849,7 @@ void gr_restoretext(GRAPH *graph) * XXX Or maybe even something more drastic ?? * It would be better to associate a color with an instance using a * vector than the vector itself, for which color is something artificial. */ + static int iplot(struct plot *pl, struct dbcomm *db) { double window; @@ -890,7 +894,9 @@ static int iplot(struct plot *pl, struct dbcomm *db) strcpy(commandline, "plot "); index = 5; resumption = FALSE; + /* Draw the grid for the first time, and plot everything. */ + lims = ft_minmax(xs, TRUE); xlims[0] = lims[0]; xlims[1] = lims[1]; @@ -951,11 +957,10 @@ 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; - } else { if (!currentgraph) /* Window was closed? */ return 0; @@ -1034,11 +1039,14 @@ static int iplot(struct plot *pl, struct dbcomm *db) /* checking for all y values */ for (v = pl->pl_dvecs; v; v = v->v_next) { + int l; + if (!(v->v_flags & VF_PLOT)) { continue; } - dy = (isreal(v) ? v->v_realdata[len - 1] : - realpart(v->v_compdata[len - 1])); + l = v->v_length - 1; + dy = (isreal(v) ? v->v_realdata[l] : + realpart(v->v_compdata[l])); if (ft_grdb) { fprintf(cp_err, "y = %G\n", dy); } @@ -1094,12 +1102,65 @@ static int iplot(struct plot *pl, struct dbcomm *db) #ifndef X_DISPLAY_MISSING gr_redraw(currentgraph); #endif + } else { + /* Draw latest point for each vector. */ + + for (v = pl->pl_dvecs; v; v = v->v_next) { + if (v->v_flags & VF_PLOT) { + if (v->v_flags & VF_EVENT_NODE) { + int last; + + if (xs->v_type != SV_TIME) { + /* There will be only one value, ignore. */ + + continue; + } + last = v->v_length - 1; + + if (len > 2 && + v->v_scale->v_realdata[last] <= + xs->v_realdata[len - 1]) { + /* No recent additions to this vector: + * draw/extend horizontal line showing last value. + * The rest is drawn in callback new_event(). + */ + + gr_point(v, + xs->v_realdata[len - 1], + v->v_realdata[last], + v->v_scale->v_realdata[last], + v->v_realdata[last], + len - 1); + } + } else { + /* Just connect the last two points. + * This won't be done with curve interpolation, + * so it might look funny. */ + + gr_point(v, + (isreal(xs) ? xs->v_realdata[len - 1] : + realpart(xs->v_compdata[len - 1])), + (isreal(v) ? v->v_realdata[len - 1] : + realpart(v->v_compdata[len - 1])), + (isreal(xs) ? xs->v_realdata[len - 2] : + realpart(xs->v_compdata[len - 2])), + (isreal(v) ? v->v_realdata[len - 2] : + realpart(v->v_compdata[len - 2])), + len - 1); + } + LC_flush(); // Disable line compression here .. +#ifdef LINE_COMPRESSION_CHECKS + LC.dv = NULL; // ... and suppress warnings. +#endif + } + } +#if BAD } else { /* Just connect the last two points. This won't be done * with curve interpolation, so it might look funny. */ for (v = pl->pl_dvecs; v; v = v->v_next) { - if (v->v_flags & VF_PLOT) { + if ((v->v_flags & VF_PLOT) && !(v->v_flags & VF_EVENT_NODE)) { gr_point(v, (isreal(xs) ? xs->v_realdata[len - 1] : realpart(xs->v_compdata[len - 1])), @@ -1116,6 +1177,7 @@ static int iplot(struct plot *pl, struct dbcomm *db) #endif } } +#endif // BAD } } DevUpdate(); @@ -1123,7 +1185,8 @@ static int iplot(struct plot *pl, struct dbcomm *db) } -static void set(struct plot *plot, struct dbcomm *db, bool value, short mode) +static void setflag(struct plot *plot, struct dbcomm *db, + bool value, short mode) { struct dvec *v; struct dbcomm *dc; @@ -1138,16 +1201,22 @@ static void set(struct plot *plot, struct dbcomm *db, bool value, short mode) } 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; @@ -1176,16 +1245,73 @@ static char *getitright(char *buf, double num) } } - static int hit, hit2; - void reset_trace(void) { hit = -1; hit2 = -1; } +/* This function is called from XSPICE whan an event node that is + * being i-plotted has a confirmed new value. + */ + +#ifdef XSPICE +static Mif_Boolean_t new_event(double when, Mif_Value_t *val, + void *ctx, int is_last) +{ + struct dbcomm *db = (struct dbcomm *)ctx; + struct dvec *v; + double value; + int last; + + if (db->db_type == DB_DEADIPLOT) + return MIF_TRUE; + v = (struct dvec *)db->db_nodename2; // Struct member re-use. + + /* Extend vectors. */ + + last = v->v_length - 1; + AddRealValueToVector(v->v_scale, when); + AddRealValueToVector(v->v_scale, when); + AddRealValueToVector(v, v->v_realdata[last]); + value = val->rvalue + db->db_value2; // Apply any plotting offset. + AddRealValueToVector(v, value); + + if (db->db_graphid) { + GRAPH *gr; + + gr = FindGraph(db->db_graphid); + if (gr) { + PushGraphContext(gr); + + /* Draw horizontal and vertical lines. */ + + gr_point(v, when, v->v_realdata[last], + v->v_scale->v_realdata[last], v->v_realdata[last], + last > 0); + 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], value, + when, value, 1); + } + LC_flush(); +#ifdef LINE_COMPRESSION_CHECKS + LC.dv = NULL; // ... and suppress warnings. +#endif + DevUpdate(); + PopGraphContext(); + } + } + return MIF_FALSE; +} +#endif void gr_iplot(struct plot *plot) { @@ -1203,6 +1329,124 @@ void gr_iplot(struct plot *plot) hit = 0; 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) { + double event_node_offset = 0, event_node_spacing; + struct dvec *v; + + /* 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; + char *offp, save_sign; + int dup = 0; + + if (dc->db_nodename1 == NULL) + continue; + + /* Duplicated names will corrupt the plot. */ + + for (dd = db; dd != dc; dd = dd->db_also) { + if (!strcmp(dc->db_nodename1, dd->db_nodename1)) { + dup = 1; + break; + } + } + 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) { + 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. + if (v->v_scale) { + v->v_scale->v_flags |= VF_PERMANENT; + vec_new(v->v_scale); + } + } + } else if (offp) { + fprintf(stderr, + "Offset (%s) ignored for analog node %s\n", + offp, dc->db_nodename1); + } + } + db->db_iteration = 0; + } +#endif if (db->db_graphid) { GRAPH *gr; @@ -1214,16 +1458,20 @@ void gr_iplot(struct plot *plot) /* Temporarily set plot flag on matching vector. */ - set(plot, db, TRUE, VF_PLOT); + setflag(plot, db, TRUE, VF_PLOT); dontpop = 0; if (iplot(plot, db)) { - /* graph just assigned */ - db->db_graphid = currentgraph->graphid; + /* Graph just assigned, place id into every struct dbcomm + * as event nodes need that. + */ + + for (dc = db; dc; dc = dc->db_also) + dc->db_graphid = currentgraph->graphid; dontpop = 1; } - set(plot, db, FALSE, VF_PLOT); + setflag(plot, db, FALSE, VF_PLOT); if (!dontpop && db->db_graphid) PopGraphContext(); @@ -1233,7 +1481,7 @@ void gr_iplot(struct plot *plot) struct dvec *v, *u; int len; - set(plot, db, TRUE, VF_PRINT); + setflag(plot, db, TRUE, VF_PRINT); len = plot->pl_scale->v_length; @@ -1289,7 +1537,7 @@ void gr_iplot(struct plot *plot) printf("\n"); } } - set(plot, db, FALSE, VF_PRINT); + setflag(plot, db, FALSE, VF_PRINT); } } } @@ -1311,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) { @@ -1323,6 +1572,7 @@ void gr_end_iplot(void) } } else if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) { + db->db_iteration = DB_NORMAL; // Reset XSPICE event plotting if (db->db_graphid) { /* get private copy of dvecs */ diff --git a/src/frontend/plotting/graphdb.c b/src/frontend/plotting/graphdb.c index 890dbc199..3f90c465f 100644 --- a/src/frontend/plotting/graphdb.c +++ b/src/frontend/plotting/graphdb.c @@ -228,8 +228,15 @@ int DestroyGraph(int id) if (db && (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL)) { - db->db_type = DB_DEADIPLOT; - /* Delete this later */ + /* Delete this later, after marking as dead. + * Entries on the node mlist are marked to terminate + * any XSPICE callbacks for event nodes. + */ + + do { + db->db_type = DB_DEADIPLOT; + db = db->db_also; + } while (db); return 0; } @@ -370,6 +377,3 @@ void PopGraphContext(void) gcstacktop = gcstacktop->next; txfree(dead); /* free allocation */ } /* end of function PopGraphContext */ - - - diff --git a/src/frontend/plotting/plotcurv.c b/src/frontend/plotting/plotcurv.c index acd07a53d..f8b98a8af 100644 --- a/src/frontend/plotting/plotcurv.c +++ b/src/frontend/plotting/plotcurv.c @@ -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; } diff --git a/src/frontend/plotting/plotit.c b/src/frontend/plotting/plotit.c index 1a207c185..c07029931 100644 --- a/src/frontend/plotting/plotit.c +++ b/src/frontend/plotting/plotit.c @@ -860,11 +860,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) && @@ -884,11 +888,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 { diff --git a/src/include/ngspice/evt.h b/src/include/ngspice/evt.h index 0f01779d5..e5021a39e 100644 --- a/src/include/ngspice/evt.h +++ b/src/include/ngspice/evt.h @@ -78,6 +78,14 @@ struct Evt_Inst_Index { int index; /* the value of the index */ }; +struct Evt_Node_Cb { + struct Evt_Node_Cb *next; + Evt_New_Value_Cb_t fn; /* Function to be called. */ + Evt_Node_Cb_Type_t type; /* Data type to pass to fn. */ + const char *member; /* For event data type's plot fn. */ + void *ctx; +}; + struct Evt_Node_Info { Evt_Node_Info_t *next; /* the next in the linked list */ char *name; /* Name of node in deck */ @@ -88,6 +96,7 @@ struct Evt_Node_Info { int num_outputs; /* Number of outputs connected to this node */ int num_insts; /* The number of insts receiving node as input */ Evt_Inst_Index_t *inst_list; /* Linked list of indexes of these instances */ + Evt_Node_Cb_t *cbs; /* New value callbacks. */ }; struct Evt_Inst_Info { @@ -195,8 +204,6 @@ struct Evt_Queue { /* ************** */ - - struct Evt_Node { Evt_Node_t *next; /* pointer to next in linked list */ Mif_Boolean_t op; /* true if computed from op analysis */ @@ -204,6 +211,7 @@ struct Evt_Node { void **output_value; /* Array of outputs posted to this node */ void *node_value; /* Resultant computed from output values */ void *inverted_value; /* Inverted copy of node_value */ + }; struct Evt_Node_Data { @@ -362,6 +370,4 @@ struct Evt_Ckt_Data { Evt_Option_t options; /* Data input on .options cards */ }; - - #endif diff --git a/src/include/ngspice/evtproto.h b/src/include/ngspice/evtproto.h index ea2bd5f92..1ec0e3228 100644 --- a/src/include/ngspice/evtproto.h +++ b/src/include/ngspice/evtproto.h @@ -136,6 +136,30 @@ bool Evtcheck_nodes( struct INPtables *stab); /* Symbol table. */ struct dvec *EVTfindvec(char *node); + +/* Set and remove call-backs on new node values. */ + +Mif_Boolean_t EVTnew_value_call(const char *node, + Evt_New_Value_Cb_t fn, + Evt_Node_Cb_Type_t type, + void *ctx); + +void EVTcancel_value_call(const char *node, + Evt_New_Value_Cb_t fn, + void *ctx); + +/* Parse a node name with member and return the node index and type. */ + +struct node_parse { + char *node; + char *member; + int udn_index; +}; + +int Evt_Parse_Node(const char *node, struct node_parse *result); + +/* Internal utility functions. */ + void Evt_purge_free_outputs(void); #endif diff --git a/src/include/ngspice/evttypes.h b/src/include/ngspice/evttypes.h index c6a59662f..dbcb920e4 100644 --- a/src/include/ngspice/evttypes.h +++ b/src/include/ngspice/evttypes.h @@ -1,6 +1,6 @@ #ifndef ngspice_EVTTYPES_H #define ngspice_EVTTYPES_H - +#include "miftypes.h" typedef struct Evt_Output_Info Evt_Output_Info_t; typedef struct Evt_Port_Info Evt_Port_Info_t; @@ -28,6 +28,11 @@ typedef struct Evt_Limit Evt_Limit_t; typedef struct Evt_Job Evt_Job_t; typedef struct Evt_Option Evt_Option_t; typedef struct Evt_Ckt_Data Evt_Ckt_Data_t; +typedef struct Evt_Node_Cb Evt_Node_Cb_t; +typedef Mif_Boolean_t (*Evt_New_Value_Cb_t)(double when, Mif_Value_t *val_p, + void *ctx, int is_last); + +typedef enum Evt_Node_Cb_Type { Evt_Cbt_Raw, Evt_Cbt_Plot} Evt_Node_Cb_Type_t; #endif diff --git a/src/include/ngspice/ftedebug.h b/src/include/ngspice/ftedebug.h index 34c215904..7543e1c6a 100644 --- a/src/include/ngspice/ftedebug.h +++ b/src/include/ngspice/ftedebug.h @@ -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. */ diff --git a/src/include/ngspice/sharedspice.h b/src/include/ngspice/sharedspice.h index a65501384..c625de068 100644 --- a/src/include/ngspice/sharedspice.h +++ b/src/include/ngspice/sharedspice.h @@ -357,6 +357,18 @@ typedef int (SendInitEvtData)(int, int, char*, char*, int, void*); int identification number of calling ngspice shared lib void* return pointer received from caller */ + +/* Upon time step finished, all events that occurred on a node. + * A non-zero return value cancels further reports for tis node. + */ + +typedef int (SendRawEvtData)(double, void *, void *, int); +/* + double event time + void* pointer to the node's value (a Digital_t for digital nodes) + void* return pointer received from caller + int zero if another event report for the same node follows +*/ #endif /* ngspice initialization, @@ -415,6 +427,32 @@ userData: pointer to user-defined data, will not be modified, but handed over back to caller during Callback, e.g. address of calling object */ IMPEXP int ngSpice_Init_Evt(SendEvtData* sevtdata, SendInitEvtData* sinitevtdata, void* userData); + +/* Request callback for every event on a specific XSPICE event node. + * A single callback function pointer is stored, with all calls directed + * to the function specified last. + * The return value identifies the node data type or is -1 on error. + * + * node name of an event node. + * srawevt pointer to callback function. + * userData pointer to user-defined data. + */ + +IMPEXP +int ngSpice_Raw_Evt(const char* node, SendRawEvtData* srawevt, void* userData); + +/* Decode raw event node data. Return 0 on success. + * evt pointer to event data (a reported node value). + * type node type index, return value from ngSpice_Raw_Evt(). + * pplotval pointer to a double, the "plotting value" is returned. NULL OK. + * printval pointer to a char pointer that is updated to point to a + * readonly string describing the value. If evt is NULL, a string + * identifying the node data type is returned. NULL is OK. + */ + +IMPEXP +int ngSpice_Decode_Evt(void* evt, int type, + double *pplotval, const char **pprintval); #endif diff --git a/src/main.c b/src/main.c index d2a4c6735..e552267e2 100644 --- a/src/main.c +++ b/src/main.c @@ -1359,6 +1359,16 @@ int main(int argc, char **argv) #elif defined(WaGauss) initw(); #endif + /* write out the ngspice start command */ + if (ft_ngdebug) + { + int ni; + fprintf(stdout, "\nNote: ngspice start command line:\n"); + for (ni = 0; ni < argc; ni++) { + fprintf(stdout, " %s", argv[ni]); + } + fprintf(stdout, "\n\n"); + } if (!ft_servermode) { @@ -1558,8 +1568,9 @@ int main(int argc, char **argv) } else { fprintf(stderr, - "Note: No \".plot\", \".print\", or \".fourier\" lines; " - "no simulations run\n"); + "Error: incomplete or empty netlist\n" + " or no \".plot\", \".print\", or \".fourier\" lines in batch mode;\n" + "no simulations run!\n"); sp_shutdown(EXIT_BAD); } diff --git a/src/sharedspice.c b/src/sharedspice.c index f48d96323..5049fd12a 100644 --- a/src/sharedspice.c +++ b/src/sharedspice.c @@ -182,6 +182,8 @@ typedef void (*sighandler)(int); #ifdef XSPICE #include "ngspice/evtshared.h" +#include "ngspice/evtproto.h" +#include "ngspice/evtudn.h" extern bool wantevtdata; #endif @@ -222,7 +224,7 @@ extern struct comm spcp_coms[]; struct comm* cp_coms = spcp_coms; /* Main options */ -static bool ft_servermode = FALSE; + bool ft_batchmode = FALSE; bool ft_pipemode = FALSE; bool rflag = FALSE; /* has rawfile */ @@ -341,6 +343,8 @@ void sh_delete_myvec(void); #ifdef XSPICE void shared_send_event(int, double, double, char *, void *, int, int); void shared_send_dict(int, int, char*, char*); + +static int evt_shim(double time, Mif_Value_t *vp, void *ctx, int last); #endif #if !defined(low_latency) @@ -397,6 +401,7 @@ static int intermj = 1; #ifdef XSPICE static SendInitEvtData* sendinitevt; static SendEvtData* sendevt; +static SendRawEvtData *sendrawevt; #endif static void* euserptr; static wordlist *shcontrols; @@ -1387,6 +1392,41 @@ int ngSpice_Init_Evt(SendEvtData* sevtdata, SendInitEvtData* sinitevtdata, void return(TRUE); } +/* Set callback address for raw XSPICE events. + * The return value identifies the node data type or is -1 on error. + */ + +IMPEXP +int ngSpice_Raw_Evt(const char* node, SendRawEvtData* srawevt, void* userData) +{ + struct node_parse np; + + if (Evt_Parse_Node(node, &np) < 0 || np.member) + return -1; // Invalid node name. + sendrawevt = srawevt; + EVTnew_value_call(node, evt_shim, Evt_Cbt_Raw, userData); + return np.udn_index; +} + +IMPEXP +int ngSpice_Decode_Evt(void* evt, int type, + double *pplotval, const char **ppprintval) +{ + if (type >= g_evt_num_udn_types) + return 1; + if (!evt) { + if (!ppprintval) + return 2; + *ppprintval = g_evt_udn_info[type]->name; + return 0; + } + if (pplotval) + g_evt_udn_info[type]->plot_val(evt, "", pplotval); + if (ppprintval) + g_evt_udn_info[type]->print_val(evt, "", (char **)ppprintval); + return 0; +} + /* Get info about the event node vector. If node_name is NULL, just delete previous data */ IMPEXP @@ -1467,7 +1507,7 @@ sh_vfprintf(FILE *f, const char *fmt, va_list args) { char buf[1024]; char *p/*, *s*/; - int nchars, /*escapes,*/ result; + int nchars; size_t size; @@ -1547,7 +1587,7 @@ sh_vfprintf(FILE *f, const char *fmt, va_list args) Spice_Init() from caller of ngspice.dll */ - result = sh_fputs(p, f); + sh_fputs(p, f); if (p != buf) tfree(p); @@ -1929,7 +1969,6 @@ void SetAnalyse( || defined (HAVE_FTIME) PerfTime timenow; /* actual time stamp */ int diffsec, diffmillisec; /* differences actual minus prev. time stamp */ - int result; /* return value from callback function */ char* s; /* outputs to callback function */ int OldPercent; /* Previous progress value */ char OldAn[128]; /* Previous analysis type */ @@ -1994,7 +2033,7 @@ void SetAnalyse( if (!strcmp(Analyse, "tran")) { if (ckt && (ckt->CKTtime > ckt->CKTfinalTime - ckt->CKTmaxStep)) { sprintf(s, "--ready--"); - result = statfcn(s, ng_ident, userptr); + statfcn(s, ng_ident, userptr); tfree(s); return; } @@ -2007,7 +2046,7 @@ void SetAnalyse( return; } sprintf( s, "--ready--"); - result = statfcn(s, ng_ident, userptr); + statfcn(s, ng_ident, userptr); tfree(s); return; } @@ -2056,7 +2095,7 @@ void SetAnalyse( } /* ouput only after a change */ if (strcmp(olds, s)) - result = statfcn(s, ng_ident, userptr); + statfcn(s, ng_ident, userptr); if(thread1) strcpy(olds1, s); else @@ -2065,11 +2104,10 @@ void SetAnalyse( tfree(s); #else char* s; - int result; static bool havesent = FALSE; if (!havesent) { s = copy("No usage info available"); - result = statfcn(s, ng_ident, userptr); + statfcn(s, ng_ident, userptr); tfree(s); havesent = TRUE; } @@ -2433,8 +2471,14 @@ sharedsync(double *pckttime, double *pcktdelta, double olddelta, double finalt, step if return value from getsync is 1. */ int retval = getsync(*pckttime, pcktdelta, olddelta, redostep, ng_ident, loc, userptr); /* never move beyond final time */ - if (*pckttime + *pcktdelta > finalt) - *pcktdelta = finalt - *pckttime - 1.1 * delmin; + if (*pckttime + *pcktdelta > finalt) { + double newdelta; + + newdelta = finalt - *pckttime - 1.1 * delmin; + if (newdelta <= 0.0) + newdelta = finalt - *pckttime; + *pcktdelta = newdelta; + } /* user has decided to redo the step, ignoring redostep being set to 0 by ngspice. */ @@ -2460,6 +2504,13 @@ void shared_send_dict(int index, int no_of_nodes, char* name, char*type) if (sendinitevt) sendinitevt(index, no_of_nodes, name, type, ng_ident, euserptr); } + +static int evt_shim(double time, Mif_Value_t *vp, void *ctx, int last) +{ + if (sendrawevt) + return sendrawevt(time, vp->pvalue, ctx, last); // Strip Mif_value. + return 1; +} #endif static int totalreset(void) diff --git a/src/xspice/evt/evtaccept.c b/src/xspice/evt/evtaccept.c index 0e68c26e6..ef5e637d7 100644 --- a/src/xspice/evt/evtaccept.c +++ b/src/xspice/evt/evtaccept.c @@ -45,6 +45,7 @@ NON-STANDARD FEATURES #include "ngspice/mif.h" #include "ngspice/evt.h" +#include "ngspice/evtudn.h" #include "ngspice/evtproto.h" @@ -160,18 +161,62 @@ void EVTaccept( num_modified = node_data->num_modified; /* Loop through list of items modified since last time */ for(i = 0; i < num_modified; i++) { + Evt_Node_t *this; + Evt_Node_Info_t *node_info; + Evt_Node_Cb_t *cb, **cbpp; + int udn_index; + /* Get the index of the node modified */ index = node_data->modified_index[i]; /* Reset the modified flag */ node_data->modified[index] = MIF_FALSE; + /* Call any value-change functions registered for this node. */ + + node_info = node_table[index]; + udn_index = node_info->udn_index; + + cbpp = &node_info->cbs; + for (;;) { + Mif_Value_t val; + + cb = *cbpp; + if (cb == NULL) + break; + + for (this = *node_data->last_step[index]; + this; + this = this->next) { + switch (cb->type) { + case Evt_Cbt_Raw: + val.pvalue = this->node_value; + break; + case Evt_Cbt_Plot: + g_evt_udn_info[udn_index]->plot_val(this->node_value, + (char *)cb->member, + &val.rvalue); + break; + } + + if ((*cb->fn)(this->step, &val, cb->ctx, !this->next)) { + /* Remove callback from chain. */ + + *cbpp = cb->next; + txfree(cb); + break; + } + } + if (this == NULL) // Normal loop exit. + cbpp = &cb->next; + } + /* Optionally store node values for later examination. * The test of CKTtime here is copied from dctran.c. * CKTinitTime is from the tstart parameter of the "tran" * command or card. */ - if (node_table[index]->save && ckt->CKTtime >= ckt->CKTinitTime && + if (node_info->save && ckt->CKTtime >= ckt->CKTinitTime && (ckt->CKTtime > 0 || !(ckt->CKTmode & MODEUIC))) { /* Update last_step for this index */ node_data->last_step[index] = node_data->tail[index]; @@ -239,3 +284,77 @@ void EVTaccept( } /* EVTaccept */ +/* Functions to set-up and cancel value-changed callbacks. */ + +Mif_Boolean_t EVTnew_value_call(const char *node, + Evt_New_Value_Cb_t fn, + Evt_Node_Cb_Type_t type, + void *ctx) +{ + struct node_parse result; + int index; + + Evt_Ckt_Data_t *evt; + CKTcircuit *ckt; + Evt_Node_Info_t *node_info; + Evt_Node_Cb_t *cb; + + index = Evt_Parse_Node(node, &result); + if (index < 0) + return MIF_FALSE; + ckt = g_mif_info.ckt; + evt = ckt->evt; + node_info = evt->info.node_table[index]; + cb = tmalloc(sizeof *cb); + cb->next = node_info->cbs; + node_info->cbs = cb; + cb->fn = fn; + cb->type = type; + cb->member = copy(result.member); + cb->ctx = ctx; + txfree(result.node); + return MIF_TRUE; +} + +void EVTcancel_value_call(const char *node, + Evt_New_Value_Cb_t fn, + void *ctx) +{ + Evt_Ckt_Data_t *evt; + CKTcircuit *ckt; + Evt_Node_Info_t **node_table, *node_info; + Evt_Node_Cb_t **cbpp, *cb; + int i, num_nodes; + + ckt = g_mif_info.ckt; + if (!ckt) + return; + evt = ckt->evt; + if (!evt) + return; + + /* Look for node name in the event-driven node list */ + + node_table = evt->info.node_table; + num_nodes = evt->counts.num_nodes; + + for (i = 0; i < num_nodes; i++) { + if (cieq(node, node_table[i]->name)) + break; + } + if (i >= num_nodes) + return; + + node_info = node_table[i]; + cbpp = &node_info->cbs; + cb = node_info->cbs; + while (cb) { + if (cb->fn == fn && cb->ctx == ctx) { + *cbpp = cb->next; + tfree(cb); + } else { + cbpp = &cb->next; + } + cb = *cbpp; + } +} diff --git a/src/xspice/evt/evtdest.c b/src/xspice/evt/evtdest.c index 4fa4dfca9..a25632f51 100644 --- a/src/xspice/evt/evtdest.c +++ b/src/xspice/evt/evtdest.c @@ -167,10 +167,12 @@ Evt_Node_Data_destroy(Evt_Ckt_Data_t *evt, Evt_Node_Data_t *node_data) for (i = 0; i < evt->counts.num_nodes; i++) { Evt_Node_Info_t *info = evt->info.node_table[i]; - Evt_Node_t *node; + Evt_Node_t *node; + node = node_data->head[i]; while (node) { Evt_Node_t *next = node->next; + Evt_Node_destroy(info, node); tfree(node); node = next; @@ -178,6 +180,7 @@ Evt_Node_Data_destroy(Evt_Ckt_Data_t *evt, Evt_Node_Data_t *node_data) node = node_data->free[i]; while (node) { Evt_Node_t *next = node->next; + Evt_Node_destroy(info, node); tfree(node); node = next; @@ -193,6 +196,7 @@ Evt_Node_Data_destroy(Evt_Ckt_Data_t *evt, Evt_Node_Data_t *node_data) for (i = 0; i < evt->counts.num_nodes; i++) { Evt_Node_Info_t *info = evt->info.node_table[i]; + Evt_Node_destroy(info, &(node_data->rhs[i])); Evt_Node_destroy(info, &(node_data->rhsold[i])); } @@ -287,6 +291,7 @@ static void Evt_Info_destroy(Evt_Info_t *info) { Evt_Inst_Info_t *inst = info->inst_list; + while (inst) { Evt_Inst_Info_t *next_inst = inst->next; tfree(inst); @@ -295,14 +300,24 @@ Evt_Info_destroy(Evt_Info_t *info) tfree(info->inst_table); Evt_Node_Info_t *nodei = info->node_list; + while (nodei) { Evt_Node_Info_t *next_nodei = nodei->next; + Evt_Node_Cb_t *cb, *cb_next; + tfree(nodei->name); + for (cb = nodei->cbs; cb; cb = cb_next) { + cb_next = cb->next; + if (cb->member) + txfree(cb->member); + tfree(cb); + } Evt_Inst_Index_t *p = nodei->inst_list; + while (p) { Evt_Inst_Index_t *next_p = p->next; - tfree(p); + txfree(p); p = next_p; } diff --git a/src/xspice/evt/evtplot.c b/src/xspice/evt/evtplot.c index a4d17842f..eca87aec5 100644 --- a/src/xspice/evt/evtplot.c +++ b/src/xspice/evt/evtplot.c @@ -45,15 +45,79 @@ NON-STANDARD FEATURES #include "ngspice/mif.h" #include "ngspice/mifproto.h" -/*saj for output */ #include "ngspice/sim.h" #include "ngspice/dvec.h" -//#include "ftedata.h" -//#include "fteconstant.h" -//#include "util.h" #include "ngspice/cpstd.h" +/* Parse member qualifier from node name and find node index. + * The node name may be qualified by a member name for nodes with + * composite values such as Digital_t, as in "node_name(state)". + */ + +int Evt_Parse_Node(const char *node, struct node_parse *result) +{ + Evt_Ckt_Data_t *evt; + CKTcircuit *ckt; + Evt_Node_Info_t **node_table; + char *name, *ptr; + int i, num_nodes; + + ckt = g_mif_info.ckt; + if (!ckt) + return -1; + evt = ckt->evt; + if (!evt) + return -1; + if (!evt->info.node_table) + return -1; + if (evt->counts.num_nodes == 0) + return -1; + + /* Make a copy of the node name. Do not free this string. */ + + name = MIFcopy((char *)node); + + /* Convert to all lower case */ + + strtolower(name); + + /* Divide into the node name and member name */ + + result->node = name; + for (ptr = name; *ptr != '\0'; ptr++) + if (*ptr == '(') + break; + + if (*ptr == '(') { + *ptr = '\0'; + ptr++; + result->member = ptr; + for( ; *ptr != '\0'; ptr++) + if (*ptr == ')') + break; + *ptr = '\0'; + } else { + result->member = NULL; + } + + /* Look for node name in the event-driven node list */ + + node_table = evt->info.node_table; + num_nodes = evt->counts.num_nodes; + + for (i = 0; i < num_nodes; i++) { + if (cieq(name, node_table[i]->name)) + break; + } + if (i >= num_nodes) { + tfree(name); + return -1; + } + result->udn_index = node_table[i]->udn_index; + return i; +} + /* EVTfindvec() @@ -79,21 +143,16 @@ keyword "all" is supplied to the plot_val routine for the member name. struct dvec *EVTfindvec( char *node) /* The node name (and optional member name) */ { - char *name; - char *member = "all"; - char *ptr; + struct node_parse result; + int index, i; + int udn_index; + int num_events; - int i; - int num_nodes; - int udn_index; - int num_events; - - Mif_Boolean_t found; - Evt_Ckt_Data_t *evt; - CKTcircuit *ckt; - Evt_Node_Info_t **node_table; - Evt_Node_t *head; - Evt_Node_t *event; + Evt_Ckt_Data_t *evt; + CKTcircuit *ckt; + Evt_Node_Info_t *node_info; + Evt_Node_t *head; + Evt_Node_t *event; double *anal_point_vec; double *value_vec; @@ -106,66 +165,24 @@ struct dvec *EVTfindvec( /* or if number of event nodes is zero. */ ckt = g_mif_info.ckt; - if(! ckt) - return(NULL); + if (!ckt) + return NULL; evt = ckt->evt; - if(! evt) - return(NULL); - if(! evt->info.node_table) - return(NULL); - if(evt->counts.num_nodes == 0) - return(NULL); + if (!evt || !evt->data.node) + return NULL; - /* Make a copy of the node name. */ - /* Do not free this string. It is assigned into the dvec structure below. */ - name = MIFcopy(node); - - /* Convert to all lower case */ - strtolower(name); - - /* Divide into the node name and member name */ - for(ptr = name; *ptr != '\0'; ptr++) - if(*ptr == '(') - break; - - if(*ptr == '(') { - *ptr = '\0'; - ptr++; - member = ptr; - for( ; *ptr != '\0'; ptr++) - if(*ptr == ')') - break; - *ptr = '\0'; - } - - /* Look for node name in the event-driven node list */ - num_nodes = evt->counts.num_nodes; - node_table = evt->info.node_table; - - for(i = 0, found = MIF_FALSE; i < num_nodes; i++) { - if(cieq(name, node_table[i]->name)) { - found = MIF_TRUE; - break; - } - } - - if(! found) { - tfree(name); - return(NULL); - } + index = Evt_Parse_Node(node, &result); + if (index < 0) + return NULL; /* Get the UDN type index */ - udn_index = node_table[i]->udn_index; - if (!evt->data.node) { -// fprintf(stderr, "Warning: No event data available! \n Simulation not yet run?\n"); - tfree(name); - return(NULL); - } + node_info = evt->info.node_table[index]; + udn_index = node_info->udn_index; /* Count the number of events */ - head = evt->data.node->head[i]; + head = evt->data.node->head[index]; for(event = head, num_events = 0; event; event = event->next) num_events++; @@ -187,9 +204,9 @@ struct dvec *EVTfindvec( /* Get the next value by calling the appropriate UDN plot_val function */ value = 0.0; - g_evt_udn_info[udn_index]->plot_val (event->node_value, - member, - &value); + g_evt_udn_info[udn_index]->plot_val(event->node_value, + result.member ? result.member : "all", + &value); /* Put the first value of the horizontal line in the vector */ anal_point_vec[i] = event->step; @@ -197,6 +214,7 @@ struct dvec *EVTfindvec( i++; } + txfree(result.node); /* Add one more point so that the line will extend to the end of the plot. */ @@ -206,20 +224,18 @@ struct dvec *EVTfindvec( /* Allocate dvec structures and assign the vectors into them. */ /* See FTE/OUTinterface.c:plotInit() for initialization example. */ - ptr = tprintf("%s_steps", name); - scale = dvec_alloc(ptr, + scale = dvec_alloc(tprintf("%s_steps", node), SV_TIME, (VF_REAL | VF_EVENT_NODE) & ~VF_PERMANENT, i, anal_point_vec); - d = dvec_alloc(name, + d = dvec_alloc(copy(node), SV_VOLTAGE, (VF_REAL | VF_EVENT_NODE) & ~VF_PERMANENT, i, value_vec); d->v_scale = scale; - /* Return the dvec */ return(d); } diff --git a/src/xspice/evt/evttermi.c b/src/xspice/evt/evttermi.c index 5f591ccef..8777738c8 100644 --- a/src/xspice/evt/evttermi.c +++ b/src/xspice/evt/evttermi.c @@ -330,6 +330,7 @@ static void EVTnode_insert( node->name = MIFcopy(node_name); node->udn_index = udn_index; node->save = MIF_TRUE; /* Backward compatible behaviour: save all. */ + node->cbs = NULL; index = ckt->evt->counts.num_nodes; (ckt->evt->counts.num_nodes)++; } diff --git a/src/xspice/idn/idndig.c b/src/xspice/idn/idndig.c index 53c21dbad..fd10243a3 100644 --- a/src/xspice/idn/idndig.c +++ b/src/xspice/idn/idndig.c @@ -210,7 +210,7 @@ static void idn_digital_plot_val(void *evt_struct, char *member, double *val) /* Output a value for the requested member of the digital struct */ - if(strcmp(member,"strength") == 0) { + if (member && strcmp(member,"strength") == 0) { /* Choose values that will not make plots lie on state plots */ switch(dig_struct->strength) {