496 lines
16 KiB
C
496 lines
16 KiB
C
/* Routines to evaluate the .measure cards.
|
|
Entry point is function do_measure(), called by fcn dosim()
|
|
from runcoms.c:335, after simulation is finished.
|
|
|
|
In addition it contains the fcn com_meas(), which provide the
|
|
interactive 'meas' command.
|
|
*/
|
|
|
|
#include "ngspice/ngspice.h"
|
|
#include "ngspice/cpdefs.h"
|
|
#include "ngspice/ftedefs.h"
|
|
#include "ngspice/dvec.h"
|
|
|
|
#include "rawfile.h"
|
|
#include "variable.h"
|
|
#include "numparam/numpaif.h"
|
|
#include "ngspice/missing_math.h"
|
|
#include "com_measure2.h"
|
|
#include "com_let.h"
|
|
#include "com_commands.h"
|
|
#include "com_display.h"
|
|
|
|
|
|
static wordlist *measure_parse_line(char *line);
|
|
|
|
extern bool ft_batchmode;
|
|
extern bool rflag;
|
|
|
|
|
|
/* measure in interactive mode:
|
|
meas command inside .control ... .endc loop or manually entered.
|
|
meas has to be followed by the standard tokens (see measure_extract_variables()).
|
|
The result is put into a vector with name "result"
|
|
*/
|
|
|
|
void
|
|
com_meas(wordlist *wl)
|
|
{
|
|
/* wl: in, input line of meas command */
|
|
char *line_in, *outvar, newvec[1000];
|
|
wordlist *wl_count, *wl_let;
|
|
|
|
char *vec_found, *token, *equal_ptr, newval[256];
|
|
wordlist *wl_index;
|
|
struct dvec *d;
|
|
int err = 0;
|
|
|
|
int fail;
|
|
double result = 0;
|
|
|
|
if (!wl) {
|
|
com_display(NULL);
|
|
return;
|
|
}
|
|
wl_count = wl;
|
|
|
|
/* check each wl entry, if it contain '=' and if the following token is
|
|
a single valued vector. If yes, replace this vector by its value.
|
|
Vectors may stem from other meas commands, or be generated elsewhere
|
|
within the .control .endc script. All other right hand side vectors are
|
|
treated in com_measure2.c. */
|
|
wl_index = wl;
|
|
|
|
while (wl_index) {
|
|
token = wl_index->wl_word;
|
|
/* find the vector vec_found, next token after each '=' sign.
|
|
May be in the next wl_word */
|
|
if (token[strlen(token) - 1] == '=') {
|
|
wl_index = wl_index->wl_next;
|
|
vec_found = wl_index->wl_word;
|
|
/* token may be already a value, maybe 'LAST', which we have to keep, or maybe a vector */
|
|
if (!cieq(vec_found, "LAST")) {
|
|
INPevaluate(&vec_found, &err, 1);
|
|
/* if not a valid number */
|
|
if (err) {
|
|
/* check if vec_found is a valid vector */
|
|
d = vec_get(vec_found);
|
|
/* Only if we have a single valued vector, replacing
|
|
of the rigt hand side does make sense */
|
|
if (d && (d->v_length == 1) && (d->v_numdims == 1)) {
|
|
/* get its value */
|
|
sprintf(newval, "%e", d->v_realdata[0]);
|
|
tfree(vec_found);
|
|
wl_index->wl_word = copy(newval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* may be inside the same wl_word */
|
|
else if ((equal_ptr = strstr(token, "=")) != NULL) {
|
|
vec_found = equal_ptr + 1;
|
|
if (!cieq(vec_found, "LAST")) {
|
|
INPevaluate(&vec_found, &err, 1);
|
|
if (err) {
|
|
d = vec_get(vec_found);
|
|
/* Only if we have a single valued vector, replacing
|
|
of the rigt hand side does make sense */
|
|
if (d && (d->v_length == 1) && (d->v_numdims == 1)) {
|
|
*equal_ptr = '\0';
|
|
sprintf(newval, "%s=%e", token, d->v_realdata[0]);
|
|
// memory leak with first part of vec_found ?
|
|
tfree(token);
|
|
wl_index->wl_word = copy(newval);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
; // nothing
|
|
}
|
|
wl_index = wl_index->wl_next;
|
|
}
|
|
|
|
line_in = wl_flatten(wl);
|
|
|
|
/* get output var name */
|
|
wl_count = wl_count->wl_next;
|
|
if (!wl_count) {
|
|
fprintf(stdout,
|
|
" meas %s failed!\n"
|
|
" unspecified output var name\n\n", line_in);
|
|
return;
|
|
}
|
|
outvar = wl_count->wl_word;
|
|
|
|
fail = get_measure2(wl, &result, NULL, FALSE);
|
|
|
|
if (fail) {
|
|
fprintf(stdout, " meas %s failed!\n\n", line_in);
|
|
return;
|
|
}
|
|
|
|
sprintf(newvec, "%s = %e", outvar, result);
|
|
wl_let = wl_cons(copy(newvec), NULL);
|
|
com_let(wl_let);
|
|
wl_free(wl_let);
|
|
tfree(line_in);
|
|
}
|
|
|
|
|
|
static bool
|
|
chkAnalysisType(char *an_type)
|
|
{
|
|
/* only support tran, dc, ac, sp analysis type for now */
|
|
if (strcmp(an_type, "tran") != 0 && strcmp(an_type, "ac") != 0 &&
|
|
strcmp(an_type, "dc") != 0 && strcmp(an_type, "sp") != 0)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* Gets pointer to double value after 'xxx=' and advances pointer of *line.
|
|
On error returns FALSE. */
|
|
static bool
|
|
get_double_value(
|
|
char **line, /*in|out: pointer to line to be parsed */
|
|
char *name, /*in: xxx e.g. 'val' from 'val=0.5' */
|
|
double *value, /*out: return value (e.g. 0.5) from 'val=0.5'*/
|
|
bool just_chk_meas /* in: just check measurement if true */
|
|
)
|
|
{
|
|
char *token = gettok(line);
|
|
bool return_val = TRUE;
|
|
char *equal_ptr, *junk;
|
|
int err = 0;
|
|
|
|
if (name && (strncmp(token, name, strlen(name)) != 0)) {
|
|
if (just_chk_meas != TRUE) fprintf(cp_err, "Error: syntax error for measure statement; expecting next field to be '%s'.\n", name);
|
|
return_val = FALSE;
|
|
} else {
|
|
/* see if '=' is last char of current token -- implies we need to read value in next token */
|
|
if (token[strlen(token) - 1] == '=') {
|
|
txfree(token);
|
|
junk = token = gettok(line);
|
|
|
|
*value = INPevaluate(&junk, &err, 1);
|
|
} else {
|
|
if ((equal_ptr = strstr(token, "=")) != NULL) {
|
|
equal_ptr += 1;
|
|
*value = INPevaluate(&equal_ptr, &err, 1);
|
|
} else {
|
|
if (just_chk_meas != TRUE)
|
|
fprintf(cp_err, "Error: syntax error for measure statement; missing '='!\n");
|
|
return_val = FALSE;
|
|
}
|
|
}
|
|
if (err) {
|
|
if (just_chk_meas != TRUE)
|
|
fprintf(cp_err, "Error: Bad value.\n");
|
|
return_val = FALSE;
|
|
}
|
|
}
|
|
txfree(token);
|
|
|
|
return return_val;
|
|
}
|
|
|
|
|
|
/* Entry point for .meas evaluation.
|
|
Called in fcn dosim() from runcoms.c:335, after simulation is finished
|
|
with chk_only set to FALSE.
|
|
Called from fcn check_autostop(),
|
|
with chk_only set to TRUE (no printouts, no params set).
|
|
This function returns TRUE if all measurements are ready and complete;
|
|
FALSE otherwise. If called with chk_only, we can exit early if we
|
|
fail a test in order to reduce execution time. */
|
|
bool
|
|
do_measure(
|
|
char *what, /*in: analysis type*/
|
|
bool chk_only /*in: TRUE if checking for "autostop", FALSE otherwise*/
|
|
)
|
|
{
|
|
struct line *meas_card, *meas_results = NULL, *end = NULL, *newcard;
|
|
char *line, *an_name, *an_type, *resname, *meastype, *str_ptr, out_line[1000];
|
|
int ok = 0;
|
|
int fail;
|
|
int num_failed = 0;
|
|
double result = 0;
|
|
bool first_time = TRUE;
|
|
bool measures_passed;
|
|
wordlist *measure_word_list;
|
|
int precision = measure_get_precision();
|
|
|
|
#ifdef HAS_PROGREP
|
|
if (!chk_only)
|
|
SetAnalyse("meas", 0);
|
|
#endif
|
|
|
|
an_name = strdup(what); /* analysis type, e.g. "tran" */
|
|
strtolower(an_name);
|
|
measure_word_list = NULL;
|
|
measures_passed = TRUE;
|
|
|
|
/* don't allow .meas if batchmode is set by -b and -r rawfile given */
|
|
if (ft_batchmode && rflag) {
|
|
fprintf(cp_err, "\nNo .measure possible in batch mode (-b) with -r rawfile set!\n");
|
|
fprintf(cp_err, "Remove rawfile and use .print or .plot or\n");
|
|
fprintf(cp_err, "select interactive mode (optionally with .control section) instead.\n\n");
|
|
return (measures_passed);
|
|
}
|
|
|
|
/* don't allow autostop if no .meas commands are given in the input file */
|
|
if ((cp_getvar("autostop", CP_BOOL, NULL)) && (ft_curckt->ci_meas == NULL)) {
|
|
fprintf(cp_err, "\nWarning: No .meas commands found!\n");
|
|
fprintf(cp_err, " Option autostop is not available, ignored!\n\n");
|
|
cp_remvar("autostop");
|
|
return (FALSE);
|
|
}
|
|
|
|
/* Evaluating the linked list of .meas cards, assembled from the input deck
|
|
by fcn inp_spsource() in inp.c:575.
|
|
A typical .meas card will contain:
|
|
parameter value
|
|
nameof card .meas(ure)
|
|
analysis type tran only tran available currently
|
|
result name myout defined by user
|
|
measurement type trig|delay|param|expr|avg|mean|max|min|rms|integ(ral)|when
|
|
|
|
The measurement type determines how to continue the .meas card.
|
|
param|expr are skipped in first pass through .meas cards and are treated in second pass,
|
|
all others are treated in fcn get_measure2() (com_measure2.c).
|
|
*/
|
|
|
|
/* first pass through .meas cards: evaluate everything except param|expr */
|
|
for (meas_card = ft_curckt->ci_meas; meas_card != NULL; meas_card = meas_card->li_next) {
|
|
line = meas_card->li_line;
|
|
|
|
txfree(gettok(&line)); /* discard .meas */
|
|
|
|
an_type = gettok(&line);
|
|
resname = gettok(&line);
|
|
meastype = gettok(&line);
|
|
|
|
if (chkAnalysisType(an_type) != TRUE) {
|
|
if (!chk_only) {
|
|
fprintf(cp_err, "Error: unrecognized analysis type '%s' for the following .meas statement on line %d:\n", an_type, meas_card->li_linenum);
|
|
fprintf(cp_err, " %s\n", meas_card->li_line);
|
|
}
|
|
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
continue;
|
|
}
|
|
/* print header before evaluating first .meas line */
|
|
else if (first_time) {
|
|
first_time = FALSE;
|
|
|
|
if (!chk_only && strcmp(an_type, "tran") == 0) {
|
|
fprintf(stdout, "\n Measurements for Transient Analysis\n\n");
|
|
}
|
|
}
|
|
|
|
/* skip param|expr measurement types for now -- will be done after other measurements */
|
|
if (strncmp(meastype, "param", 5) == 0 || strncmp(meastype, "expr", 4) == 0)
|
|
continue;
|
|
|
|
/* skip .meas line, if analysis type from line and name of analysis performed differ */
|
|
if (strcmp(an_name, an_type) != 0) {
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
continue;
|
|
}
|
|
|
|
/* New way of processing measure statements using common code
|
|
in fcn get_measure2() (com_measure2.c)*/
|
|
out_line[0] = '\0';
|
|
measure_word_list = measure_parse_line(meas_card->li_line);
|
|
if (measure_word_list) {
|
|
fail = get_measure2(measure_word_list, &result, out_line, chk_only);
|
|
if (fail) {
|
|
measures_passed = FALSE;
|
|
if (!chk_only)
|
|
fprintf(stderr, " %s failed!\n\n", meas_card->li_line);
|
|
num_failed++;
|
|
if (chk_only) {
|
|
/* added for speed - cleanup last parse and break */
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
wl_free(measure_word_list);
|
|
break;
|
|
}
|
|
} else {
|
|
if (!chk_only)
|
|
nupa_add_param(resname, result);
|
|
}
|
|
wl_free(measure_word_list);
|
|
} else {
|
|
measures_passed = FALSE;
|
|
num_failed++;
|
|
}
|
|
|
|
if (!chk_only) {
|
|
newcard = alloc(struct line);
|
|
newcard->li_line = strdup(out_line);
|
|
newcard->li_next = NULL;
|
|
|
|
if (meas_results == NULL) {
|
|
meas_results = end = newcard;
|
|
} else {
|
|
end->li_next = newcard;
|
|
end = newcard;
|
|
}
|
|
}
|
|
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
|
|
} /* end of for loop (first pass through .meas lines) */
|
|
|
|
if (chk_only) {
|
|
tfree(an_name);
|
|
return (measures_passed);
|
|
}
|
|
/* second pass through .meas cards: now do param|expr .meas statements */
|
|
newcard = meas_results;
|
|
for (meas_card = ft_curckt->ci_meas; meas_card != NULL; meas_card = meas_card->li_next) {
|
|
line = meas_card->li_line;
|
|
|
|
txfree(gettok(&line)); /* discard .meas */
|
|
|
|
an_type = gettok(&line);
|
|
resname = gettok(&line);
|
|
meastype = gettok(&line);
|
|
|
|
if (chkAnalysisType(an_type) != TRUE) {
|
|
if (!chk_only) {
|
|
fprintf(cp_err, "Error: unrecognized analysis type '%s' for the following .meas statement on line %d:\n", an_type, meas_card->li_linenum);
|
|
fprintf(cp_err, " %s\n", meas_card->li_line);
|
|
}
|
|
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
continue;
|
|
}
|
|
if (strcmp(an_name, an_type) != 0) {
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
continue;
|
|
}
|
|
|
|
if (strncmp(meastype, "param", 5) != 0 && strncmp(meastype, "expr", 4) != 0) {
|
|
|
|
if (!chk_only)
|
|
fprintf(stdout, "%s", newcard->li_line);
|
|
end = newcard;
|
|
newcard = newcard->li_next;
|
|
|
|
txfree(end->li_line);
|
|
txfree(end);
|
|
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
continue;
|
|
}
|
|
|
|
if (!chk_only)
|
|
fprintf(stdout, "%-20s=", resname);
|
|
|
|
if (!chk_only) {
|
|
ok = nupa_eval(meas_card->li_line, meas_card->li_linenum, meas_card->li_linenum_orig);
|
|
|
|
if (ok) {
|
|
str_ptr = strstr(meas_card->li_line, meastype);
|
|
if (!get_double_value(&str_ptr, meastype, &result, chk_only)) {
|
|
if (!chk_only)
|
|
fprintf(stdout, " failed\n");
|
|
} else {
|
|
if (!chk_only)
|
|
fprintf(stdout, " %.*e\n", precision, result);
|
|
nupa_add_param(resname, result);
|
|
}
|
|
} else {
|
|
if (!chk_only)
|
|
fprintf(stdout, " failed\n");
|
|
}
|
|
}
|
|
txfree(an_type);
|
|
txfree(resname);
|
|
txfree(meastype);
|
|
}
|
|
|
|
if (!chk_only)
|
|
fprintf(stdout, "\n");
|
|
|
|
txfree(an_name);
|
|
|
|
fflush(stdout);
|
|
|
|
return(measures_passed);
|
|
}
|
|
|
|
|
|
/* called from dctran.c:470, if timepoint is accepted.
|
|
Returns TRUE if measurement (just a check, no output) has been successful.
|
|
If TRUE is returned, transient simulation is stopped.
|
|
Returns TRUE if "autostop" has been set as an option and if do_measure
|
|
passes all tests and thereby returns TRUE. 'what' is set to "tran". */
|
|
|
|
bool
|
|
check_autostop(char* what)
|
|
{
|
|
bool flag = FALSE;
|
|
|
|
if (cp_getvar("autostop", CP_BOOL, NULL))
|
|
flag = do_measure(what, TRUE);
|
|
|
|
return flag;
|
|
}
|
|
|
|
|
|
/* parses the .meas line into a wordlist (without leading .meas) */
|
|
static wordlist *
|
|
measure_parse_line(char *line)
|
|
{
|
|
size_t len; /* length of string */
|
|
wordlist *wl; /* build a word list - head of list */
|
|
wordlist *new_item; /* single item of a list */
|
|
char *item; /* parsed item */
|
|
char *long_str; /* concatenated string */
|
|
char *extra_item; /* extra item */
|
|
|
|
wl = NULL;
|
|
txfree(gettok(&line));
|
|
do {
|
|
item = gettok(&line);
|
|
if (!(item))
|
|
break;
|
|
|
|
len = strlen(item);
|
|
if (item[len-1] == '=') {
|
|
/* We can't end on an equal append the next piece */
|
|
extra_item = gettok(&line);
|
|
if (!(extra_item))
|
|
break;
|
|
|
|
len += strlen(extra_item) + 2;
|
|
long_str = TMALLOC(char, len);
|
|
sprintf(long_str, "%s%s", item, extra_item);
|
|
txfree(item);
|
|
txfree(extra_item);
|
|
item = long_str;
|
|
}
|
|
new_item = wl_cons(item, NULL);
|
|
wl = wl_append(wl, new_item);
|
|
} while (line && *line);
|
|
|
|
return (wl);
|
|
}
|