From 1445a51a2e5b15eb79082ff9a95703bccf94123c Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Fri, 28 Mar 2025 14:20:32 +0100 Subject: [PATCH 01/14] New command 'save nointernals' to suppress outputting and saving of internal device nodes, espicially needed for PSP OSDI model. Output file size reduction is e.g. from 20GB to 200MB. --- src/frontend/outitf.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/frontend/outitf.c b/src/frontend/outitf.c index 87329841d..c294e146b 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); From 13bbe47020c97665f1229039f174546c4dd2f932 Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Fri, 4 Apr 2025 15:33:43 +0200 Subject: [PATCH 02/14] Debug printout of start-up command line --- src/main.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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); } From 3bf94321e6a18a4389f18dcce4765da876005607 Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Thu, 10 Apr 2025 23:51:34 +0200 Subject: [PATCH 03/14] Improved error message, not using internal 'circbyline' --- src/frontend/inpcom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index c60a16d6c..dbe1faa9a 100644 --- a/src/frontend/inpcom.c +++ b/src/frontend/inpcom.c @@ -8988,7 +8988,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); From c264b71e2284bf67f66330254c177081b090853b Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Sun, 6 Apr 2025 12:47:08 +0200 Subject: [PATCH 04/14] re-enable adding variables (path names) to list variable "sourcepath" --- src/frontend/inpcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index dbe1faa9a..83e3af6a4 100644 --- a/src/frontend/inpcom.c +++ b/src/frontend/inpcom.c @@ -9722,8 +9722,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)) { From f4963b1ada188b82871474308d6324e9f9949d33 Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Sat, 12 Apr 2025 18:12:54 +0200 Subject: [PATCH 05/14] MS Windows: Get a canonical paths name: Important if path length exceeds MAX_PATH, might happen when using PDKs Patch provided by kreijstal --- src/frontend/inpcom.c | 163 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 154 insertions(+), 9 deletions(-) diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index 83e3af6a4..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 */ @@ -9749,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 From 33f206b916f86b5138e3b113c23a958127edbf3a Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Sun, 23 Mar 2025 16:46:55 +0000 Subject: [PATCH 06/14] Add two new XSPICE functions: EVTnew_value_call() and EVTcancel_value_call(). EVTnew_value_call() specifies a function to be called for each confirmed new value produced on an XSPICE event node. To be used to add event node support for iplot. --- src/include/ngspice/evt.h | 14 ++- src/include/ngspice/evtproto.h | 22 +++++ src/include/ngspice/evttypes.h | 7 +- src/xspice/evt/evtaccept.c | 121 +++++++++++++++++++++++- src/xspice/evt/evtdest.c | 19 +++- src/xspice/evt/evtplot.c | 163 ++++++++++++++++++--------------- src/xspice/evt/evttermi.c | 1 + 7 files changed, 266 insertions(+), 81 deletions(-) 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..033db4226 100644 --- a/src/include/ngspice/evtproto.h +++ b/src/include/ngspice/evtproto.h @@ -136,6 +136,28 @@ 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); + +/* Internal utility functions. */ + void Evt_purge_free_outputs(void); +/* Parse a node name with member and find the node index. */ + +struct node_parse { + char *node; + char *member; +}; + +int Evt_Parse_Node(const char *node, struct node_parse *result); #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/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..dafce5c7d 100644 --- a/src/xspice/evt/evtplot.c +++ b/src/xspice/evt/evtplot.c @@ -45,15 +45,78 @@ 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; + } + return i; +} + /* EVTfindvec() @@ -79,21 +142,17 @@ 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; + char *name; + 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; @@ -105,67 +164,26 @@ struct dvec *EVTfindvec( /* Exit immediately if event-driven stuff not allocated yet, */ /* or if number of event nodes is zero. */ + index = Evt_Parse_Node(node, &result); + if (index < 0) + return NULL; + name = result.node; ckt = g_mif_info.ckt; - 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); - /* 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) { + if (!evt->data.node) { tfree(name); - return(NULL); + 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 +205,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; @@ -206,8 +224,7 @@ 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", name), SV_TIME, (VF_REAL | VF_EVENT_NODE) & ~VF_PERMANENT, i, anal_point_vec); 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)++; } From db39671ae2c0ab636fac896bed5790d6a591a271 Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Thu, 27 Jul 2023 04:51:54 +0100 Subject: [PATCH 07/14] Do not strip event value member names (like "digital_node(strength") from the name when creating a vector from event node history. --- src/xspice/evt/evtplot.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/xspice/evt/evtplot.c b/src/xspice/evt/evtplot.c index dafce5c7d..d0e272496 100644 --- a/src/xspice/evt/evtplot.c +++ b/src/xspice/evt/evtplot.c @@ -142,7 +142,6 @@ 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; struct node_parse result; int index, i; int udn_index; @@ -164,17 +163,16 @@ struct dvec *EVTfindvec( /* Exit immediately if event-driven stuff not allocated yet, */ /* or if number of event nodes is zero. */ + ckt = g_mif_info.ckt; + if (!ckt) + return NULL; + evt = ckt->evt; + if (!evt || !evt->data.node) + return NULL; + index = Evt_Parse_Node(node, &result); if (index < 0) return NULL; - name = result.node; - ckt = g_mif_info.ckt; - evt = ckt->evt; - - if (!evt->data.node) { - tfree(name); - return NULL; - } /* Get the UDN type index */ @@ -215,6 +213,7 @@ struct dvec *EVTfindvec( i++; } + txfree(result.node); /* Add one more point so that the line will extend to the end of the plot. */ @@ -224,19 +223,18 @@ struct dvec *EVTfindvec( /* Allocate dvec structures and assign the vectors into them. */ /* See FTE/OUTinterface.c:plotInit() for initialization example. */ - scale = dvec_alloc(tprintf("%s_steps", name), + 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); } From 1d435daed74a982417bf3bba1f99b56c91c50f8e Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Fri, 29 Dec 2023 16:21:53 +0000 Subject: [PATCH 08/14] Support use of XSPICE event nodes with iplot. In graf.c, local function set() is renamed setflag() to avoid conflict with a newly-visible global function with the same name. --- src/frontend/outitf.c | 20 +-- src/frontend/outitf.h | 2 +- src/frontend/plotting/graf.c | 236 ++++++++++++++++++++++++++++---- src/frontend/plotting/graphdb.c | 14 +- src/xspice/idn/idndig.c | 2 +- 5 files changed, 228 insertions(+), 46 deletions(-) diff --git a/src/frontend/outitf.c b/src/frontend/outitf.c index c294e146b..d47e61632 100644 --- a/src/frontend/outitf.c +++ b/src/frontend/outitf.c @@ -623,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); } @@ -630,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); } @@ -737,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); @@ -1231,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 */ @@ -1258,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..396a27a23 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); @@ -159,6 +162,7 @@ int gr_init(double *xlims, double *ylims, /* The size of the screen. */ /* restore background color from previous graph, e.g. for zooming, it will be used in NewViewport(graph) */ + if (prevgraph > 0) { pgraph = FindGraph(prevgraph); if (pgraph) @@ -309,7 +313,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 +545,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 +558,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 +593,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 +613,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 +850,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 +895,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]; @@ -955,7 +962,6 @@ static int iplot(struct plot *pl, struct dbcomm *db) } } inited = 1; - } else { if (!currentgraph) /* Window was closed? */ return 0; @@ -1034,11 +1040,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 +1103,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 +1178,7 @@ static int iplot(struct plot *pl, struct dbcomm *db) #endif } } +#endif // BAD } } DevUpdate(); @@ -1123,7 +1186,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; @@ -1176,16 +1240,72 @@ 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; + int last; + + if (db->db_type == DB_DEADIPLOT) + return MIF_TRUE; + v = vec_fromplot(db->db_nodename1, plot_cur); + if (!v || !v->v_scale) + return MIF_TRUE; + + /* Extend vectors. */ + + last = v->v_length - 1; + AddRealValueToVector(v->v_scale, when); + AddRealValueToVector(v->v_scale, when); + AddRealValueToVector(v, v->v_realdata[last]); + AddRealValueToVector(v, val->rvalue); + + 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, val->rvalue, when, v->v_realdata[last], 1); + if (is_last) { + struct dvec *xs; + + /* Extend horizontally to the end of the time-step. */ + + xs = v->v_plot->pl_scale; + gr_point(v, xs->v_realdata[xs->v_length - 1], val->rvalue, + when, val->rvalue, 1); + } + LC_flush(); +#ifdef LINE_COMPRESSION_CHECKS + LC.dv = NULL; // ... and suppress warnings. +#endif + PopGraphContext(); + } + } + return MIF_FALSE; +} +#endif void gr_iplot(struct plot *plot) { @@ -1203,6 +1323,57 @@ 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) { + struct dvec *v; + + /* First call: find any XSPICE event nodes in the node + * list and set up plotting. There is a parallel path + * for pushing new event values into their corresponding + * vectors and plotting them. + */ + + for (dc = db; dc; dc = dc->db_also) { + struct dbcomm *dd; + int dup = 0; + + 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; + + v = vec_fromplot(dc->db_nodename1, plot); + if (!v) { + 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)) { + 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); + } + } + EVTnew_value_call(dc->db_nodename1, new_event, + Evt_Cbt_Plot, dc); + } + } + db->db_iteration = 1; + } +#endif if (db->db_graphid) { GRAPH *gr; @@ -1214,16 +1385,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 +1408,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 +1464,7 @@ void gr_iplot(struct plot *plot) printf("\n"); } } - set(plot, db, FALSE, VF_PRINT); + setflag(plot, db, FALSE, VF_PRINT); } } } @@ -1323,11 +1498,12 @@ void gr_end_iplot(void) } } else if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) { + db->db_iteration = 0; // Reset XSPICE event plotting if (db->db_graphid) { /* get private copy of dvecs */ - graph = FindGraph(db->db_graphid); + graph = FindGraph(db->db_graphid); link = graph->plotdata; while (link) { 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/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) { From cd6784a079b4a76b147a18ad3c68f6bbdcbb675c Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Sat, 29 Mar 2025 19:08:32 +0000 Subject: [PATCH 09/14] Event nodes can be plotted with offset by "iplot", like the digitop option for "plot". Offsets may be explicit or automatic with "-o". A variable to set the offset is shared with "plot". In plotcurv.c suppress some warnings that should not apply to event nodes and do not falsely claim that some vectors have only one x-value. --- src/frontend/breakp.c | 40 +++++++--- src/frontend/commands.c | 6 +- src/frontend/plotting/graf.c | 132 ++++++++++++++++++++++++------- src/frontend/plotting/plotcurv.c | 44 +++++++---- src/frontend/plotting/plotit.c | 12 ++- src/include/ngspice/ftedebug.h | 8 +- 6 files changed, 180 insertions(+), 62 deletions(-) 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/plotting/graf.c b/src/frontend/plotting/graf.c index 396a27a23..3fbfcb237 100644 --- a/src/frontend/plotting/graf.c +++ b/src/frontend/plotting/graf.c @@ -162,7 +162,6 @@ int gr_init(double *xlims, double *ylims, /* The size of the screen. */ /* restore background color from previous graph, e.g. for zooming, it will be used in NewViewport(graph) */ - if (prevgraph > 0) { pgraph = FindGraph(prevgraph); if (pgraph) @@ -958,7 +957,7 @@ static int iplot(struct plot *pl, struct dbcomm *db) for (v = pl->pl_dvecs; v; v = v->v_next) { if (v->v_flags & VF_PLOT) { gr_start_internal(v, FALSE); - ft_graf(v, xs, TRUE); + ft_graf(v, v->v_scale ? v->v_scale : xs, TRUE); } } inited = 1; @@ -1119,7 +1118,7 @@ static int iplot(struct plot *pl, struct dbcomm *db) last = v->v_length - 1; if (len > 2 && - v->v_scale->v_realdata[last] < + v->v_scale->v_realdata[last] <= xs->v_realdata[len - 1]) { /* No recent additions to this vector: * draw/extend horizontal line showing last value. @@ -1202,16 +1201,22 @@ static void setflag(struct plot *plot, struct dbcomm *db, } for (dc = db; dc; dc = dc->db_also) { - if (dc->db_nodename1 == NULL) - continue; - v = vec_fromplot(dc->db_nodename1, plot); - if (!v || v->v_plot != plot) { - if (!eq(dc->db_nodename1, "0") && value) { - fprintf(cp_err, "Warning: node %s non-existent in %s.\n", - dc->db_nodename1, plot->pl_name); - /* note: XXX remove it from dbs, so won't get further errors */ + if (db->db_type == DB_IPLOT) { /* Vector cached in db_nodename2. */ + v = (struct dvec *)dc->db_nodename2; + if (!v) + continue; + } else { + if (dc->db_nodename1 == NULL) + continue; + v = vec_fromplot(dc->db_nodename1, plot); + if (!v || v->v_plot != plot) { + if (!eq(dc->db_nodename1, "0") && value) { + fprintf(cp_err, "Warning: node %s non-existent in %s.\n", + dc->db_nodename1, plot->pl_name); + /* note: XXX remove it from dbs, so no further errors. */ + } + continue; } - continue; } if (value) v->v_flags |= mode; @@ -1258,13 +1263,12 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val, { struct dbcomm *db = (struct dbcomm *)ctx; struct dvec *v; + double value; int last; if (db->db_type == DB_DEADIPLOT) return MIF_TRUE; - v = vec_fromplot(db->db_nodename1, plot_cur); - if (!v || !v->v_scale) - return MIF_TRUE; + v = (struct dvec *)db->db_nodename2; // Struct member re-use. /* Extend vectors. */ @@ -1272,7 +1276,8 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val, AddRealValueToVector(v->v_scale, when); AddRealValueToVector(v->v_scale, when); AddRealValueToVector(v, v->v_realdata[last]); - AddRealValueToVector(v, val->rvalue); + value = val->rvalue + db->db_value2; // Apply any plotting offset. + AddRealValueToVector(v, value); if (db->db_graphid) { GRAPH *gr; @@ -1286,20 +1291,21 @@ static Mif_Boolean_t new_event(double when, Mif_Value_t *val, gr_point(v, when, v->v_realdata[last], v->v_scale->v_realdata[last], v->v_realdata[last], last > 0); - gr_point(v, when, val->rvalue, when, v->v_realdata[last], 1); + gr_point(v, when, value, when, v->v_realdata[last], 1); if (is_last) { struct dvec *xs; /* Extend horizontally to the end of the time-step. */ xs = v->v_plot->pl_scale; - gr_point(v, xs->v_realdata[xs->v_length - 1], val->rvalue, - when, val->rvalue, 1); + gr_point(v, xs->v_realdata[xs->v_length - 1], value, + when, value, 1); } LC_flush(); #ifdef LINE_COMPRESSION_CHECKS LC.dv = NULL; // ... and suppress warnings. #endif + DevUpdate(); PopGraphContext(); } } @@ -1324,18 +1330,35 @@ void gr_iplot(struct plot *plot) for (db = dbs; db; db = db->db_next) { if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) { #ifdef XSPICE - if (db->db_iteration == 0) { + if (db->db_iteration > 0) { + double event_node_offset = 0, event_node_spacing; struct dvec *v; - /* First call: find any XSPICE event nodes in the node + /* First call: set up event nodes spacing. */ + + if (db->db_iteration == DB_AUTO_OFFSET) { + /* Magic value: automatically offset traces for + *event nodes. + */ + + if (!cp_getvar("event_node_spacing", CP_REAL, + &event_node_spacing, 0)) { + event_node_spacing = 1.5; + } + } else { + event_node_spacing = 0; + } + + /* Find any XSPICE event nodes in the node * list and set up plotting. There is a parallel path * for pushing new event values into their corresponding * vectors and plotting them. */ for (dc = db; dc; dc = dc->db_also) { - struct dbcomm *dd; - int dup = 0; + struct dbcomm *dd; + char *offp, save_sign; + int dup = 0; if (dc->db_nodename1 == NULL) continue; @@ -1351,14 +1374,62 @@ void gr_iplot(struct plot *plot) if (dup) continue; + /* Check for a nodename that is an expression. */ + + offp = strchr(dc->db_nodename1, '+'); + if (!offp) + offp = strchr(dc->db_nodename1, '-'); + if (offp > dc->db_nodename1) { + save_sign = *offp; + *offp = '\0'; // Trim to bare name. + } + v = vec_fromplot(dc->db_nodename1, plot); - if (!v) { + if (v) { + dc->db_nodename2 = (char *)v; // Save link to vector. + } else { fprintf(cp_err, "Warning: node %s non-existent in %s.\n", dc->db_nodename1, plot->pl_name); } if (v && (v->v_flags & VF_EVENT_NODE)) { + /* Ask event simulator to call back with new values. */ + + EVTnew_value_call(dc->db_nodename1, new_event, + Evt_Cbt_Plot, dc); + + if (offp > dc->db_nodename1) { + *offp = save_sign; + dc->db_value2 = atof(offp); // Offset to value. + event_node_offset = // New auto-offset. + dc->db_value2 + event_node_spacing; + } else if (event_node_spacing) { + char new_name[256]; + + /* Add offset to vector names. + * Ugly, but only here can event nodes + * be identified. + */ + + snprintf(new_name, sizeof new_name, "%s+%g", + dc->db_nodename1, event_node_offset); + tfree(v->v_name); + v->v_name = copy(new_name); + + dc->db_value2 = event_node_offset; + event_node_offset += event_node_spacing; + } + + if (dc->db_value2) { + int i; + + /* Adjust existing values. */ + + for (i = 0; i < v->v_length; ++i) + v->v_realdata[i] += dc->db_value2; + } + if ((v->v_flags & VF_PERMANENT) == 0) { vec_new(v); v->v_flags |= VF_PERMANENT; // Make it findable. @@ -1367,11 +1438,13 @@ void gr_iplot(struct plot *plot) vec_new(v->v_scale); } } - EVTnew_value_call(dc->db_nodename1, new_event, - Evt_Cbt_Plot, dc); + } else if (offp) { + fprintf(stderr, + "Offset (%s) ignored for analog node %s\n", + offp, dc->db_nodename1); } } - db->db_iteration = 1; + db->db_iteration = 0; } #endif if (db->db_graphid) { @@ -1486,6 +1559,7 @@ void gr_end_iplot(void) prev = NULL; for (db = dbs; db; prev = db, db = next) { + db->db_nodename2 = NULL; // Forget link. next = db->db_next; if (db->db_type == DB_DEADIPLOT) { if (db->db_graphid) { @@ -1498,12 +1572,12 @@ void gr_end_iplot(void) } } else if (db->db_type == DB_IPLOT || db->db_type == DB_IPLOTALL) { - db->db_iteration = 0; // Reset XSPICE event plotting + db->db_iteration = DB_NORMAL; // Reset XSPICE event plotting if (db->db_graphid) { /* get private copy of dvecs */ - graph = FindGraph(db->db_graphid); + link = graph->plotdata; while (link) { 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 449b0fec1..c54d81f3f 100644 --- a/src/frontend/plotting/plotit.c +++ b/src/frontend/plotting/plotit.c @@ -854,11 +854,15 @@ bool plotit(wordlist *wl, const char *hcopy, const char *devname) /* Add n * spacing (e.g. 1.5) to digital event node based vectors */ if (digitop) { - double spacing = 1.5; + double spacing; double nn = 0.; int ii = 0, jj = 0; - for (d = vecs; d; d = d->v_link2) { + if (!cp_getvar("plot_auto_spacing", CP_REAL, &spacing, 0) || + spacing < 0.0) { + spacing = 1.5; + } + for (d = vecs; d; d = d->v_link2) { if ((d->v_flags & VF_EVENT_NODE) && !(d->v_flags & VF_PERMANENT) && d->v_scale && (d->v_scale->v_flags & VF_EVENT_NODE) && @@ -878,11 +882,11 @@ bool plotit(wordlist *wl, const char *hcopy, const char *devname) if (!ylim) { ylim = TMALLOC(double, 2); ylim[0] = 0; - /* make ylim[1] a multiple of 2*1.5 */ + /* make ylim[1] a multiple of 2 * spacing. */ if (jj % 2 == 0) ylim[1] = nn; else - ylim[1] = nn + 1.5; + ylim[1] = nn + spacing; } /* re-scaled plot */ else { 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. */ From 31e0c8e6cfa1331806b277e0aafb3291781e0979 Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Tue, 25 Mar 2025 17:50:24 +0000 Subject: [PATCH 10/14] Add two new functions to the shared library API. ngSpice_Raw_Evt() requests a callback that returns all the XSPICE events for a specific node that occurred during the last timestep. ngSpice_Decode_Evt() provides numeric and string versions of the event data. Also fix some warnings in sharedspice.c. --- src/include/ngspice/evtproto.h | 12 +++--- src/include/ngspice/sharedspice.h | 38 +++++++++++++++++++ src/sharedspice.c | 63 ++++++++++++++++++++++++++----- src/xspice/evt/evtplot.c | 1 + 4 files changed, 100 insertions(+), 14 deletions(-) diff --git a/src/include/ngspice/evtproto.h b/src/include/ngspice/evtproto.h index 033db4226..1ec0e3228 100644 --- a/src/include/ngspice/evtproto.h +++ b/src/include/ngspice/evtproto.h @@ -148,16 +148,18 @@ void EVTcancel_value_call(const char *node, Evt_New_Value_Cb_t fn, void *ctx); -/* Internal utility functions. */ - -void Evt_purge_free_outputs(void); - -/* Parse a node name with member and find the node index. */ +/* 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/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/sharedspice.c b/src/sharedspice.c index f48d96323..059e4a3e2 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; } @@ -2460,6 +2498,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/evtplot.c b/src/xspice/evt/evtplot.c index d0e272496..eca87aec5 100644 --- a/src/xspice/evt/evtplot.c +++ b/src/xspice/evt/evtplot.c @@ -114,6 +114,7 @@ int Evt_Parse_Node(const char *node, struct node_parse *result) tfree(name); return -1; } + result->udn_index = node_table[i]->udn_index; return i; } From b8b83b1601300724790a631b6418f95d2b282acf Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Thu, 27 Mar 2025 08:54:24 +0000 Subject: [PATCH 11/14] Add an extended shared library test program with additional local commands to exercise the API. --- examples/shared/README | 93 +++++ examples/shared/shx.c | 829 +++++++++++++++++++++++++++++++++++++++++ examples/shared/t.cir | 34 ++ examples/shared/t.shx | 23 ++ 4 files changed, 979 insertions(+) create mode 100644 examples/shared/README create mode 100644 examples/shared/shx.c create mode 100644 examples/shared/t.cir create mode 100755 examples/shared/t.shx 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 From 46c5a8d375fdf6e3b878ad75a2857f371d7c153b Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Sat, 29 Mar 2025 14:20:52 +0000 Subject: [PATCH 12/14] Fix for an error message seen when testing the shx shared-library program: do not allow a negative timestep. --- src/sharedspice.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sharedspice.c b/src/sharedspice.c index 059e4a3e2..5049fd12a 100644 --- a/src/sharedspice.c +++ b/src/sharedspice.c @@ -2471,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. */ From a91cd8292cb28a29ba479a45186bf02d5a89cf04 Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Mon, 31 Mar 2025 10:26:41 +0100 Subject: [PATCH 13/14] Rename pll-xspice.cir to shared-pll-xspice.cir to prepare for split. --- examples/xspice/pll/{pll-xspice.cir => shared-pll-xspice.cir} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/xspice/pll/{pll-xspice.cir => shared-pll-xspice.cir} (100%) diff --git a/examples/xspice/pll/pll-xspice.cir b/examples/xspice/pll/shared-pll-xspice.cir similarity index 100% rename from examples/xspice/pll/pll-xspice.cir rename to examples/xspice/pll/shared-pll-xspice.cir From 78581e3ad4988b7a390aa79dbdf3aa3f601a7d1a Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Mon, 31 Mar 2025 10:33:49 +0100 Subject: [PATCH 14/14] Re-make pll-xspice.cir as a wrapper around shared-pll-xspice.cir, behaviour as before. Add similar pll-digital-iplot.cir as a demonstration of iplot with analogue and digital nodes. --- examples/xspice/pll/pll-digital-iplot.cir | 20 ++++++++++++++++++++ examples/xspice/pll/pll-xspice.cir | 15 +++++++++++++++ examples/xspice/pll/shared-pll-xspice.cir | 15 ++------------- 3 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 examples/xspice/pll/pll-digital-iplot.cir create mode 100644 examples/xspice/pll/pll-xspice.cir 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 new file mode 100644 index 000000000..3f66f1b11 --- /dev/null +++ b/examples/xspice/pll/pll-xspice.cir @@ -0,0 +1,15 @@ +* pll circuit using xspice code models: version with analog-only iplot +* output frequency 400 MHz +* locked to a 1 or 10 MHz reference + +.include shared-pll-xspice.cir + +.control +save cont s1 s2 u1n d1 v.xlf.vdd#branch; to save memory +iplot -d 4000 cont +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 +.endc +.end diff --git a/examples/xspice/pll/shared-pll-xspice.cir b/examples/xspice/pll/shared-pll-xspice.cir index a1df101f1..b3ea64049 100644 --- a/examples/xspice/pll/shared-pll-xspice.cir +++ b/examples/xspice/pll/shared-pll-xspice.cir @@ -1,4 +1,5 @@ -* pll circuit using xspice code models +* 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 @@ -62,16 +63,6 @@ abridge-w1 [d_divout d_ref d_Un d_D] [s1 s2 u1n d1] dac1 ; change to d_u or d_Un + input_load = 5.0e-12 t_rise = 1e-10 + t_fall = 1e-10) -.control -save cont s1 s2 u1n d1 v.xlf.vdd#branch; to save memory -iplot -d 4000 cont -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 @@ -140,5 +131,3 @@ plot v.xlf.vdd#branch xlimit 4u 5u ylimit -8m 2m +Ua1= 4.312e-9 Ub1= 6.65e-19 Uc1= 0 +Kt1l=0 - -.end