1 /* Routines to evaluate the .measure cards.
2    Entry point is function do_measure(), called by fcn dosim()
3    from runcoms.c:335, after simulation is finished.
4 
5    In addition it contains the fcn com_meas(), which provide the
6    interactive 'meas' command.
7 */
8 
9 #include "ngspice/ngspice.h"
10 #include "ngspice/cpdefs.h"
11 #include "ngspice/ftedefs.h"
12 #include "ngspice/dvec.h"
13 
14 #include "rawfile.h"
15 #include "variable.h"
16 #include "numparam/numpaif.h"
17 #include "ngspice/missing_math.h"
18 #include "com_measure2.h"
19 #include "com_let.h"
20 #include "com_commands.h"
21 #include "com_display.h"
22 
23 
24 static wordlist *measure_parse_line(char *line);
25 
26 extern bool ft_batchmode;
27 extern bool rflag;
28 
29 
30 /* measure in interactive mode:
31    meas command inside .control ... .endc loop or manually entered.
32    meas has to be followed by the standard tokens (see measure_extract_variables()).
33    The result is put into a vector with name "result"
34 */
35 
36 void
com_meas(wordlist * wl)37 com_meas(wordlist *wl)
38 {
39     /* wl: in, input line of meas command */
40     char *line_in, *outvar;
41     wordlist *wl_count, *wl_let;
42 
43     char *vec_found, *token, *equal_ptr;
44     wordlist *wl_index;
45     struct dvec *d;
46     int err = 0;
47 
48     int fail;
49     double result = 0;
50 
51     if (!wl) {
52         com_display(NULL);
53         return;
54     }
55     wl_count = wl;
56 
57     /* check each wl entry, if it contain '=' and if the following token is
58        a single valued vector. If yes, replace this vector by its value.
59        Vectors may stem from other meas commands, or be generated elsewhere
60        within the .control .endc script. All other right hand side vectors are
61        treated in com_measure2.c. */
62     wl_index = wl;
63 
64     while (wl_index) {
65         token = wl_index->wl_word;
66         /* find the vector vec_found, next token after each '=' sign.
67            May be in the next wl_word */
68         if (token[strlen(token) - 1] == '=') {
69             wl_index = wl_index->wl_next;
70             if (wl_index == NULL) {
71                 line_in = wl_flatten(wl);
72                 fprintf(stderr, "\nError: meas failed due to missing token in \n    meas %s \n\n", line_in);
73                 tfree(line_in);
74                 return;
75             }
76             vec_found = wl_index->wl_word;
77             /* token may be already a value, maybe 'LAST', which we have to keep, or maybe a vector */
78             if (!cieq(vec_found, "LAST")) {
79                 INPevaluate(&vec_found, &err, 1);
80                 /* if not a valid number */
81                 if (err) {
82                     /* check if vec_found is a valid vector */
83                     d = vec_get(vec_found);
84                     /* Only if we have a single valued vector, replacing
85                        of the rigt hand side does make sense */
86                     if (d && (d->v_length == 1) && (d->v_numdims == 1)) {
87                         /* get its value */
88                         wl_index->wl_word = tprintf("%e", d->v_realdata[0]);
89                         tfree(vec_found);
90                     }
91                 }
92             }
93         }
94         /* may be inside the same wl_word */
95         else if ((equal_ptr = strchr(token, '=')) != NULL) {
96             vec_found = equal_ptr + 1;
97             if (!cieq(vec_found, "LAST")) {
98                 INPevaluate(&vec_found, &err, 1);
99                 if (err) {
100                     d = vec_get(vec_found);
101                     /* Only if we have a single valued vector, replacing
102                     of the rigt hand side does make sense */
103                     if (d && (d->v_length == 1) && (d->v_numdims == 1)) {
104                         int lhs_len = (int)(equal_ptr - token);
105                         wl_index->wl_word =
106                             tprintf("%.*s=%e", lhs_len, token, d->v_realdata[0]);
107                         tfree(token);
108                     }
109                 }
110             }
111         } else {
112             ;                   // nothing
113         }
114         wl_index = wl_index->wl_next;
115     }
116 
117     line_in = wl_flatten(wl);
118 
119     /* get output var name */
120     wl_count = wl_count->wl_next;
121     if (!wl_count) {
122         fprintf(stdout,
123                 " meas %s failed!\n"
124                 "   unspecified output var name\n\n", line_in);
125         tfree(line_in);
126         return;
127     }
128     outvar = wl_count->wl_word;
129 
130     fail = get_measure2(wl, &result, NULL, FALSE);
131 
132     if (fail) {
133         fprintf(stdout, " meas %s failed!\n\n", line_in);
134         tfree(line_in);
135         return;
136     }
137 
138     wl_let = wl_cons(tprintf("%s = %e", outvar, result), NULL);
139     com_let(wl_let);
140     wl_free(wl_let);
141     tfree(line_in);
142 }
143 
144 
145 static bool
chkAnalysisType(char * an_type)146 chkAnalysisType(char *an_type)
147 {
148     /* only support tran, dc, ac, sp analysis type for now */
149     if (strcmp(an_type, "tran") != 0 && strcmp(an_type, "ac") != 0 &&
150             strcmp(an_type, "dc") != 0 && strcmp(an_type, "sp") != 0)
151         return FALSE;
152     else
153         return TRUE;
154 }
155 
156 
157 /* Gets pointer to double value after 'xxx=' and advances pointer of *line.
158    On error returns FALSE. */
159 static bool
get_double_value(char ** line,char * name,double * value,bool just_chk_meas)160 get_double_value(
161     char **line,   /*in|out: pointer to line to be parsed */
162     char *name,    /*in: xxx e.g. 'val' from 'val=0.5' */
163     double *value, /*out: return value (e.g. 0.5) from 'val=0.5'*/
164     bool just_chk_meas /* in: just check measurement if true */
165 )
166 {
167     char *token     = gettok(line);
168     bool return_val = TRUE;
169     char *equal_ptr, *junk;
170     int  err = 0;
171 
172     if (name && (strncmp(token, name, strlen(name)) != 0)) {
173         if (just_chk_meas != TRUE) fprintf(cp_err, "Error: syntax error for measure statement; expecting next field to be '%s'.\n", name);
174         return_val = FALSE;
175     } else {
176         /* see if '=' is last char of current token -- implies we need to read value in next token */
177         if (token[strlen(token) - 1] == '=') {
178             txfree(token);
179             junk = token = gettok(line);
180 
181             *value = INPevaluate(&junk, &err, 1);
182         } else {
183             if ((equal_ptr = strchr(token, '=')) != NULL) {
184                 equal_ptr += 1;
185                 *value = INPevaluate(&equal_ptr, &err, 1);
186             } else {
187                 if (just_chk_meas != TRUE)
188                     fprintf(cp_err, "Error: syntax error for measure statement; missing '='!\n");
189                 return_val = FALSE;
190             }
191         }
192         if (err) {
193             if (just_chk_meas != TRUE)
194                 fprintf(cp_err, "Error: Bad value.\n");
195             return_val = FALSE;
196         }
197     }
198     txfree(token);
199 
200     return return_val;
201 }
202 
203 
204 /* Entry point for .meas evaluation.
205    Called in fcn dosim() from runcoms.c:335, after simulation is finished
206    with chk_only set to FALSE.
207    Called from fcn check_autostop(),
208    with chk_only set to TRUE (no printouts, no params set).
209    This function returns TRUE if all measurements are ready and complete;
210    FALSE otherwise.  If called with chk_only, we can exit early if we
211    fail a test in order to reduce execution time.  */
212 bool
do_measure(char * what,bool chk_only)213 do_measure(
214     char *what,   /*in: analysis type*/
215     bool chk_only /*in: TRUE if checking for "autostop", FALSE otherwise*/
216 )
217 {
218     struct card *meas_card, *meas_results = NULL, *end = NULL, *newcard;
219     char        *line, *an_name, *an_type, *resname, *meastype, *str_ptr, out_line[1000];
220     int         ok = 0;
221     int         fail;
222     int         num_failed = 0;
223     double      result = 0;
224     bool        first_time = TRUE;
225     bool        measures_passed;
226     wordlist    *measure_word_list;
227     int         precision = measure_get_precision();
228 
229 #ifdef HAS_PROGREP
230     if (!chk_only)
231         SetAnalyse("meas", 0);
232 #endif
233 
234     an_name = copy(what); /* analysis type, e.g. "tran" */
235     strtolower(an_name);
236     measure_word_list = NULL;
237     measures_passed = TRUE;
238 
239     /* don't allow .meas if batchmode is set by -b and -r rawfile given */
240     if (ft_batchmode && rflag) {
241         fprintf(cp_err, "\nNo .measure possible in batch mode (-b) with -r rawfile set!\n");
242         fprintf(cp_err, "Remove rawfile and use .print or .plot or\n");
243         fprintf(cp_err, "select interactive mode (optionally with .control section) instead.\n\n");
244         return (measures_passed);
245     }
246 
247     /* don't allow autostop if no .meas commands are given in the input file */
248     if ((cp_getvar("autostop", CP_BOOL, NULL, 0)) && (ft_curckt->ci_meas == NULL)) {
249         fprintf(cp_err, "\nWarning: No .meas commands found!\n");
250         fprintf(cp_err, "  Option autostop is not available, ignored!\n\n");
251         cp_remvar("autostop");
252         return (FALSE);
253     }
254 
255     /* Evaluating the linked list of .meas cards, assembled from the input deck
256        by fcn inp_spsource() in inp.c:575.
257        A typical .meas card will contain:
258        parameter        value
259        nameof card      .meas(ure)
260        analysis type    tran        only tran available currently
261        result name      myout       defined by user
262        measurement type trig|delay|param|expr|avg|mean|max|min|rms|integ(ral)|when
263 
264        The measurement type determines how to continue the .meas card.
265        param|expr are skipped in first pass through .meas cards and are treated in second pass,
266        all others are treated in fcn get_measure2() (com_measure2.c).
267        */
268 
269     /* first pass through .meas cards: evaluate everything except param|expr */
270     for (meas_card = ft_curckt->ci_meas; meas_card != NULL; meas_card = meas_card->nextcard) {
271         line = meas_card->line;
272 
273         line = nexttok(line); /* discard .meas */
274 
275         an_type = gettok(&line);
276         resname = gettok(&line);
277         meastype = gettok(&line);
278 
279         if (chkAnalysisType(an_type) != TRUE) {
280             if (!chk_only) {
281                 fprintf(cp_err, "Error: unrecognized analysis type '%s' for the following .meas statement on line %d:\n", an_type, meas_card->linenum);
282                 fprintf(cp_err, "       %s\n", meas_card->line);
283             }
284 
285             txfree(an_type);
286             txfree(resname);
287             txfree(meastype);
288             continue;
289         }
290         /* print header before evaluating first .meas line */
291         else if (first_time) {
292             first_time = FALSE;
293 
294             if (!chk_only && strcmp(an_type, "tran") == 0) {
295                 fprintf(stdout, "\n  Measurements for Transient Analysis\n\n");
296             }
297         }
298 
299         /* skip param|expr measurement types for now -- will be done after other measurements */
300         if (strncmp(meastype, "param", 5) == 0 || strncmp(meastype, "expr", 4) == 0) {
301             txfree(an_type);
302             txfree(resname);
303             txfree(meastype);
304             continue;
305         }
306 
307         /* skip .meas line, if analysis type from line and name of analysis performed differ */
308         if (strcmp(an_name, an_type) != 0) {
309             txfree(an_type);
310             txfree(resname);
311             txfree(meastype);
312             continue;
313         }
314 
315         /* New way of processing measure statements using common code
316            in fcn get_measure2() (com_measure2.c)*/
317         out_line[0] = '\0';
318         measure_word_list = measure_parse_line(meas_card->line);
319         if (measure_word_list) {
320             fail = get_measure2(measure_word_list, &result, out_line, chk_only);
321             if (fail) {
322                 measures_passed = FALSE;
323                 if (!chk_only)
324                     fprintf(stderr, " %s failed!\n\n", meas_card->line);
325                 num_failed++;
326                 if (chk_only) {
327                     /* added for speed - cleanup last parse and break */
328                     txfree(an_type);
329                     txfree(resname);
330                     txfree(meastype);
331                     wl_free(measure_word_list);
332                     break;
333                 }
334             } else {
335                 if (!chk_only)
336                     nupa_add_param(resname, result);
337             }
338             wl_free(measure_word_list);
339         } else {
340             measures_passed = FALSE;
341             num_failed++;
342         }
343 
344         if (!chk_only) {
345             newcard          = TMALLOC(struct card, 1);
346             newcard->line = copy(out_line);
347             newcard->nextcard = NULL;
348 
349             if (meas_results == NULL) {
350                 meas_results = end = newcard;
351             } else {
352                 end->nextcard = newcard;
353                 end          = newcard;
354             }
355         }
356 
357         txfree(an_type);
358         txfree(resname);
359         txfree(meastype);
360 
361     } /* end of for loop (first pass through .meas lines) */
362 
363     if (chk_only) {
364         tfree(an_name);
365         return (measures_passed);
366     }
367     /* second pass through .meas cards: now do param|expr .meas statements */
368     newcard = meas_results;
369     for (meas_card = ft_curckt->ci_meas; meas_card != NULL; meas_card = meas_card->nextcard) {
370         line = meas_card->line;
371 
372         line = nexttok(line); /* discard .meas */
373 
374         an_type = gettok(&line);
375         resname = gettok(&line);
376         meastype = gettok(&line);
377 
378         if (chkAnalysisType(an_type) != TRUE) {
379             if (!chk_only) {
380                 fprintf(cp_err, "Error: unrecognized analysis type '%s' for the following .meas statement on line %d:\n", an_type, meas_card->linenum);
381                 fprintf(cp_err, "       %s\n", meas_card->line);
382             }
383 
384             txfree(an_type);
385             txfree(resname);
386             txfree(meastype);
387             continue;
388         }
389         if (strcmp(an_name, an_type) != 0) {
390             txfree(an_type);
391             txfree(resname);
392             txfree(meastype);
393             continue;
394         }
395 
396         if (strncmp(meastype, "param", 5) != 0 && strncmp(meastype, "expr", 4) != 0) {
397 
398             if (!chk_only)
399                 fprintf(stdout, "%s", newcard->line);
400             end     = newcard;
401             newcard = newcard->nextcard;
402 
403             txfree(end->line);
404             txfree(end);
405 
406             txfree(an_type);
407             txfree(resname);
408             txfree(meastype);
409             continue;
410         }
411 
412         if (!chk_only)
413             fprintf(stdout, "%-20s=", resname);
414 
415         if (!chk_only) {
416             ok = nupa_eval(meas_card);
417 
418             if (ok) {
419                 str_ptr = strstr(meas_card->line, meastype);
420                 if (!get_double_value(&str_ptr, meastype, &result, chk_only)) {
421                     if (!chk_only)
422                         fprintf(stdout, "   failed\n");
423                 } else {
424                     if (!chk_only)
425                         fprintf(stdout, "  %.*e\n", precision, result);
426                     nupa_add_param(resname, result);
427                 }
428             } else {
429                 if (!chk_only)
430                     fprintf(stdout, "   failed\n");
431             }
432         }
433         txfree(an_type);
434         txfree(resname);
435         txfree(meastype);
436     }
437 
438     if (!chk_only)
439         fprintf(stdout, "\n");
440 
441     txfree(an_name);
442 
443     fflush(stdout);
444 
445     return(measures_passed);
446 }
447 
448 
449 /* called from dctran.c:470, if timepoint is accepted.
450    Returns TRUE if measurement (just a check, no output) has been successful.
451    If TRUE is returned, transient simulation is stopped.
452    Returns TRUE if "autostop" has been set as an option and if do_measure
453    passes all tests and thereby returns TRUE.  'what' is set to "tran". */
454 
455 bool
check_autostop(char * what)456 check_autostop(char* what)
457 {
458     bool flag = FALSE;
459 
460     if (cp_getvar("autostop", CP_BOOL, NULL, 0))
461         flag = do_measure(what, TRUE);
462 
463     return flag;
464 }
465 
466 
467 /* parses the .meas line into a wordlist (without leading .meas) */
468 static wordlist *
measure_parse_line(char * line)469 measure_parse_line(char *line)
470 {
471     size_t len;                         /* length of string */
472     wordlist *wl;                       /* build a word list - head of list */
473     wordlist *new_item;                 /* single item of a list */
474     char *item;                         /* parsed item */
475     char *long_str;                     /* concatenated string */
476     char *extra_item;                   /* extra item */
477 
478     wl = NULL;
479     line = nexttok(line);
480     do {
481         item = gettok(&line);
482         if (!(item))
483             break;
484 
485         len = strlen(item);
486         if (item[len-1] == '=') {
487             /* We can't end on an equal append the next piece */
488             extra_item = gettok(&line);
489             if (!(extra_item))
490                 break;
491 
492             len += strlen(extra_item) + 2;
493             long_str = TMALLOC(char, len);
494             sprintf(long_str, "%s%s", item, extra_item);
495             txfree(item);
496             txfree(extra_item);
497             item = long_str;
498         }
499         new_item = wl_cons(item, NULL);
500         wl = wl_append(wl, new_item);
501     } while (line && *line);
502 
503     return (wl);
504 }
505