1704 lines
58 KiB
C
1704 lines
58 KiB
C
/**********
|
|
Copyright 1990 Regents of the University of California. All rights reserved.
|
|
Author: 1985 Wayne A. Christopher
|
|
**********/
|
|
|
|
/*
|
|
* Stuff for dealing with spice input decks and command scripts, and
|
|
* the listing routines.
|
|
*/
|
|
|
|
#include "ngspice/ngspice.h"
|
|
|
|
#include "ngspice/cktdefs.h"
|
|
#include "ngspice/cpdefs.h"
|
|
#include "ngspice/inpdefs.h"
|
|
#include "ngspice/ftedefs.h"
|
|
#include "ngspice/dvec.h"
|
|
#include "ngspice/fteinp.h"
|
|
#include "inp.h"
|
|
|
|
#include "runcoms.h"
|
|
#include "inpcom.h"
|
|
#include "circuits.h"
|
|
#include "completion.h"
|
|
#include "variable.h"
|
|
#include "breakp2.h"
|
|
#include "dotcards.h"
|
|
#include "../misc/util.h" /* ngdirname() */
|
|
#include "../misc/mktemp.h"
|
|
#include "../misc/misc_time.h"
|
|
#include "subckt.h"
|
|
#include "spiceif.h"
|
|
#include "com_let.h"
|
|
#include "com_commands.h"
|
|
|
|
#ifdef XSPICE
|
|
#include "ngspice/ipctiein.h"
|
|
#include "ngspice/enh.h"
|
|
#endif
|
|
|
|
#include "numparam/numpaif.h"
|
|
|
|
|
|
#define line_free(line, flag) \
|
|
do { \
|
|
line_free_x(line, flag); \
|
|
line = NULL; \
|
|
} while(0)
|
|
|
|
static char *upper(register char *string);
|
|
static bool doedit(char *filename);
|
|
static struct line *com_options = NULL;
|
|
static void cktislinear(CKTcircuit *ckt, struct line *deck);
|
|
static void dotifeval(struct line *deck);
|
|
static int inp_parse_temper(struct line *deck);
|
|
static void inp_parse_temper_trees(void);
|
|
|
|
static void inp_savecurrents(struct line *deck, struct line *options, wordlist **wl, wordlist *con);
|
|
|
|
void line_free_x(struct line *deck, bool recurse);
|
|
void create_circbyline(char *line);
|
|
|
|
void inp_evaluate_temper(void);
|
|
|
|
extern bool ft_batchmode;
|
|
|
|
/* structure used to save expression parse trees for .model and
|
|
* device instance lines
|
|
*/
|
|
|
|
struct pt_temper {
|
|
char *expression;
|
|
wordlist *wl;
|
|
wordlist *wlend;
|
|
INPparseTree *pt;
|
|
struct pt_temper *next;
|
|
};
|
|
|
|
/*
|
|
* create an unique artificial *unusable* FILE ptr
|
|
* meant to be used with Xprintf() only to eventually
|
|
* redirect output to the `out_vprintf()' family
|
|
*/
|
|
|
|
static FILE *cp_more;
|
|
static FILE *cp_more = (FILE*) &cp_more;
|
|
|
|
static void
|
|
Xprintf(FILE *fdst, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
|
|
if (fdst == cp_more)
|
|
out_vprintf(fmt, ap);
|
|
else
|
|
vfprintf(fdst, fmt, ap);
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
/* Do a listing. Use is listing [expanded] [logical] [physical] [deck] */
|
|
void
|
|
com_listing(wordlist *wl)
|
|
{
|
|
int type = LS_LOGICAL;
|
|
bool expand = FALSE, do_param_listing = FALSE;
|
|
char *s;
|
|
|
|
if (ft_curckt) { /* if there is a current circuit . . . . */
|
|
while (wl) {
|
|
s = wl->wl_word;
|
|
if (strcmp(s, "param") == 0) {
|
|
do_param_listing = TRUE;
|
|
} else {
|
|
switch (*s) {
|
|
case 'l':
|
|
case 'L':
|
|
type = LS_LOGICAL;
|
|
break;
|
|
case 'p':
|
|
case 'P':
|
|
type = LS_PHYSICAL;
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
type = LS_DECK;
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
expand = TRUE;
|
|
break;
|
|
default:
|
|
fprintf(cp_err, "Error: bad listing type %s\n", s);
|
|
return; /* SJB - don't go on after an error */
|
|
}
|
|
}
|
|
wl = wl->wl_next;
|
|
}
|
|
|
|
if (do_param_listing) {
|
|
nupa_list_params(cp_out);
|
|
} else {
|
|
if (type != LS_DECK)
|
|
fprintf(cp_out, "\t%s\n\n", ft_curckt->ci_name);
|
|
inp_list(cp_out,
|
|
expand ? ft_curckt->ci_deck : ft_curckt->ci_origdeck,
|
|
ft_curckt->ci_options, type);
|
|
}
|
|
} else {
|
|
fprintf(cp_err, "Error: no circuit loaded.\n");
|
|
}
|
|
}
|
|
|
|
|
|
/* returns inp_casefix() or NULL */
|
|
static char *
|
|
upper(char *string)
|
|
{
|
|
static char buf[BSIZE_SP];
|
|
|
|
if (string) {
|
|
strncpy(buf, string, BSIZE_SP - 1);
|
|
buf[BSIZE_SP - 1] = '\0';
|
|
inp_casefix(buf);
|
|
} else {
|
|
strcpy(buf, "<null>");
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
|
|
/* Provide an input listing on the specified file of the given card
|
|
* deck. The listing should be of either LS_PHYSICAL or LS_LOGICAL or
|
|
* LS_DECK lines as specified by the type parameter. */
|
|
void
|
|
inp_list(FILE *file, struct line *deck, struct line *extras, int type)
|
|
{
|
|
struct line *here;
|
|
struct line *there;
|
|
bool renumber;
|
|
bool useout = (file == cp_out);
|
|
int i = 1;
|
|
|
|
/* gtri - wbk - 03/07/91 - Don't use 'more' type output if ipc enabled */
|
|
#ifdef XSPICE
|
|
if (g_ipc.enabled)
|
|
useout = FALSE;
|
|
#endif
|
|
/* gtri - end - 03/07/91 */
|
|
|
|
if (useout) {
|
|
out_init();
|
|
file = cp_more;
|
|
}
|
|
|
|
renumber = cp_getvar("renumber", CP_BOOL, NULL);
|
|
|
|
if (type == LS_LOGICAL) {
|
|
top1:
|
|
for (here = deck; here; here = here->li_next) {
|
|
if (renumber)
|
|
here->li_linenum = i;
|
|
if (ciprefix(".end", here->li_line) && !isalpha(here->li_line[4]))
|
|
continue;
|
|
if (*here->li_line != '*') {
|
|
Xprintf(file, "%6d : %s\n", here->li_linenum, upper(here->li_line));
|
|
if (here->li_error)
|
|
Xprintf(file, "%s\n", here->li_error);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (extras) {
|
|
deck = extras;
|
|
extras = NULL;
|
|
goto top1;
|
|
}
|
|
|
|
Xprintf(file, "%6d : .end\n", i);
|
|
|
|
} else if ((type == LS_PHYSICAL) || (type == LS_DECK)) {
|
|
|
|
top2:
|
|
for (here = deck; here; here = here->li_next) {
|
|
if ((here->li_actual == NULL) || (here == deck)) {
|
|
if (renumber)
|
|
here->li_linenum = i;
|
|
if (ciprefix(".end", here->li_line) && !isalpha(here->li_line[4]))
|
|
continue;
|
|
if (type == LS_PHYSICAL)
|
|
Xprintf(file, "%6d : %s\n",
|
|
here->li_linenum, upper(here->li_line));
|
|
else
|
|
Xprintf(file, "%s\n", upper(here->li_line));
|
|
if (here->li_error && (type == LS_PHYSICAL))
|
|
Xprintf(file, "%s\n", here->li_error);
|
|
} else {
|
|
for (there = here->li_actual; there; there = there->li_next) {
|
|
there->li_linenum = i++;
|
|
if (ciprefix(".end", here->li_line) && isalpha(here->li_line[4]))
|
|
continue;
|
|
if (type == LS_PHYSICAL)
|
|
Xprintf(file, "%6d : %s\n",
|
|
there->li_linenum, upper(there->li_line));
|
|
else
|
|
Xprintf(file, "%s\n", upper(there->li_line));
|
|
if (there->li_error && (type == LS_PHYSICAL))
|
|
Xprintf(file, "%s\n", there->li_error);
|
|
}
|
|
here->li_linenum = i;
|
|
}
|
|
i++;
|
|
}
|
|
if (extras) {
|
|
deck = extras;
|
|
extras = NULL;
|
|
goto top2;
|
|
}
|
|
if (type == LS_PHYSICAL)
|
|
Xprintf(file, "%6d : .end\n", i);
|
|
else
|
|
Xprintf(file, ".end\n");
|
|
} else {
|
|
fprintf(cp_err, "inp_list: Internal Error: bad type %d\n", type);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Free memory used by a line.
|
|
* If recurse is TRUE then recursively free all lines linked via the li_next field.
|
|
* If recurse is FALSE free only this line.
|
|
* All lines linked via the li_actual field are always recursivly freed.
|
|
* SJB - 22nd May 2001
|
|
*/
|
|
void
|
|
line_free_x(struct line *deck, bool recurse)
|
|
{
|
|
while (deck) {
|
|
struct line *next_deck = deck->li_next;
|
|
line_free_x(deck->li_actual, TRUE);
|
|
tfree(deck->li_line);
|
|
tfree(deck->li_error);
|
|
tfree(deck);
|
|
if (!recurse)
|
|
return;
|
|
deck = next_deck;
|
|
}
|
|
}
|
|
|
|
|
|
/* The routine to source a spice input deck. We read the deck in, take
|
|
* out the front-end commands, and create a CKT structure. Also we
|
|
* filter out the following cards: .save, .width, .four, .print, and
|
|
* .plot, to perform after the run is over.
|
|
* Then, we run dodeck, which parses up the deck. */
|
|
void
|
|
inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile)
|
|
/* arguments:
|
|
* *fp = pointer to the input file
|
|
* comfile = whether it is a command file. Values are TRUE/FALSE
|
|
* *filename = name of input file
|
|
* intfile = whether input is from internal array. Values are TRUE/FALSE
|
|
*/
|
|
{
|
|
struct line *deck, *dd, *ld, *prev_param = NULL, *prev_card = NULL;
|
|
struct line *realdeck = NULL, *options = NULL, *curr_meas = NULL;
|
|
char *tt = NULL, name[BSIZE_SP], *s, *t, *temperature = NULL;
|
|
double testemp = 0.0;
|
|
bool commands = FALSE;
|
|
wordlist *wl = NULL, *end = NULL, *wl_first = NULL;
|
|
wordlist *controls = NULL, *pre_controls = NULL;
|
|
FILE *lastin, *lastout, *lasterr;
|
|
double temperature_value;
|
|
|
|
double startTime, endTime;
|
|
|
|
/* read in the deck from a file */
|
|
char *dir_name = ngdirname(filename ? filename : ".");
|
|
|
|
startTime = seconds();
|
|
deck = inp_readall(fp, dir_name, comfile, intfile);
|
|
endTime = seconds();
|
|
tfree(dir_name);
|
|
|
|
/* if nothing came back from inp_readall, just close fp and return to caller */
|
|
if (!deck) { /* MW. We must close fp always when returning */
|
|
if (!intfile)
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
if (!comfile) {
|
|
options = inp_getopts(deck);
|
|
|
|
realdeck = inp_deckcopy(deck);
|
|
|
|
/* Save the title before INPgetTitle gets it. */
|
|
tt = copy(deck->li_line);
|
|
if (!deck->li_next)
|
|
fprintf(cp_err, "Warning: no lines in input\n");
|
|
}
|
|
if (!intfile)
|
|
fclose(fp);
|
|
|
|
/* Now save the IO context and start a new control set. After we
|
|
are done with the source we'll put the old file descriptors
|
|
back. I guess we could use a FILE stack, but since this
|
|
routine is recursive anyway. */
|
|
lastin = cp_curin;
|
|
lastout = cp_curout;
|
|
lasterr = cp_curerr;
|
|
cp_curin = cp_in;
|
|
cp_curout = cp_out;
|
|
cp_curerr = cp_err;
|
|
|
|
cp_pushcontrol();
|
|
|
|
/* We should now go through the deck and execute front-end
|
|
* commands and remove them. Front-end commands are enclosed by
|
|
* the cards .control and .endc, unless comfile is TRUE, in which
|
|
* case every line must be a front-end command. There are too
|
|
* many problems with matching the first word on the line. */
|
|
ld = deck;
|
|
if (comfile) {
|
|
/* Process each command, except 'option' which is assembled
|
|
in a list and ingnored here */
|
|
for (dd = deck; dd; dd = ld) {
|
|
ld = dd->li_next;
|
|
if ((dd->li_line[0] == '*') && (dd->li_line[1] != '#'))
|
|
continue;
|
|
if (!ciprefix(".control", dd->li_line) && !ciprefix(".endc", dd->li_line)) {
|
|
if (dd->li_line[0] == '*')
|
|
cp_evloop(dd->li_line + 2);
|
|
/* option line stored but not processed */
|
|
else if (ciprefix("option", dd->li_line))
|
|
com_options = inp_getoptsc(dd->li_line, com_options);
|
|
else
|
|
cp_evloop(dd->li_line);
|
|
}
|
|
}
|
|
/* free the control deck */
|
|
line_free(deck, TRUE);
|
|
/* set to NULL to allow generation of a new dbs */
|
|
/* do this here and in the 'else' branch of 'if (comfile)' */
|
|
dbs = NULL;
|
|
ft_dotsaves();
|
|
} /* end if (comfile) */
|
|
|
|
else { /* must be regular deck . . . . */
|
|
/* loop through deck and handle control cards */
|
|
for (dd = deck->li_next; dd; dd = ld->li_next) {
|
|
/* get temp from deck */
|
|
if (ciprefix(".temp", dd->li_line)) {
|
|
s = dd->li_line + 5;
|
|
while (isspace(*s))
|
|
s++;
|
|
if (temperature)
|
|
txfree(temperature);
|
|
temperature = strdup(s);
|
|
}
|
|
/* Ignore comment lines, but not lines begining with '*#',
|
|
but remove them, if they are in a .control ... .endc section */
|
|
s = dd->li_line;
|
|
while (isspace(*s))
|
|
s++;
|
|
if ((*s == '*') && ((s != dd->li_line) || (s[1] != '#'))) {
|
|
if (commands) {
|
|
/* Remove comment lines in control sections, so they don't
|
|
* get considered as circuits. */
|
|
ld->li_next = dd->li_next;
|
|
line_free(dd, FALSE);
|
|
continue;
|
|
}
|
|
ld = dd;
|
|
continue;
|
|
}
|
|
|
|
/* Put the first token from line into s */
|
|
strncpy(name, dd->li_line, BSIZE_SP);
|
|
for (s = name; *s && isspace(*s); s++)
|
|
;
|
|
for (t = s; *t && !isspace(*t); t++)
|
|
;
|
|
*t = '\0';
|
|
|
|
if (ciprefix(".control", dd->li_line)) {
|
|
ld->li_next = dd->li_next;
|
|
line_free(dd, FALSE); /* SJB - free this line's memory */
|
|
if (commands)
|
|
fprintf(cp_err, "Warning: redundant .control card\n");
|
|
else
|
|
commands = TRUE;
|
|
} else if (ciprefix(".endc", dd->li_line)) {
|
|
ld->li_next = dd->li_next;
|
|
line_free(dd, FALSE); /* SJB - free this line's memory */
|
|
if (commands)
|
|
commands = FALSE;
|
|
else
|
|
fprintf(cp_err, "Warning: misplaced .endc card\n");
|
|
} else if (commands || prefix("*#", dd->li_line)) {
|
|
/* assemble all commands starting with pre_ after stripping pre_,
|
|
to be executed before circuit parsing */
|
|
if (ciprefix("pre_", dd->li_line)) {
|
|
s = copy(dd->li_line + 4);
|
|
pre_controls = wl_cons(s, pre_controls);
|
|
}
|
|
/* assemble all other commands to be executed after circuit parsing */
|
|
else {
|
|
/* special control lines outside of .control section*/
|
|
if (prefix("*#", dd->li_line)) {
|
|
s = copy(dd->li_line + 2);
|
|
/* all commands from within .control section */
|
|
} else {
|
|
s = dd->li_line;
|
|
dd->li_line = NULL; /* SJB - prevent line_free() freeing the string (now pointed at by wl->wl_word) */
|
|
}
|
|
controls = wl_cons(s, controls);
|
|
}
|
|
ld->li_next = dd->li_next;
|
|
line_free(dd, FALSE);
|
|
} else if (!*dd->li_line) {
|
|
/* So blank lines in com files don't get considered as circuits. */
|
|
ld->li_next = dd->li_next;
|
|
line_free(dd, FALSE);
|
|
} else {
|
|
/* lines .width, .four, .plot, .print, .save added to wl_first, removed from deck */
|
|
/* lines .op, .meas, .tf added to wl_first */
|
|
inp_casefix(s); /* s: first token from line */
|
|
inp_casefix(dd->li_line);
|
|
if (eq(s, ".width") ||
|
|
ciprefix(".four", s) ||
|
|
eq(s, ".plot") ||
|
|
eq(s, ".print") ||
|
|
eq(s, ".save") ||
|
|
eq(s, ".op") ||
|
|
ciprefix(".meas", s) ||
|
|
eq(s, ".tf"))
|
|
{
|
|
wl_append_word(&wl_first, &end, copy(dd->li_line));
|
|
|
|
if (!eq(s, ".op") && !eq(s, ".tf") && !ciprefix(".meas", s)) {
|
|
ld->li_next = dd->li_next;
|
|
line_free(dd, FALSE);
|
|
} else {
|
|
ld = dd;
|
|
}
|
|
} else {
|
|
ld = dd;
|
|
}
|
|
}
|
|
} /* end for (dd = deck->li_next . . . . */
|
|
|
|
/* Now that the deck is loaded, do the pre commands, if there are any,
|
|
before the circuit structure is set up */
|
|
if (pre_controls) {
|
|
pre_controls = wl_reverse(pre_controls);
|
|
for (wl = pre_controls; wl; wl = wl->wl_next)
|
|
cp_evloop(wl->wl_word);
|
|
wl_free(pre_controls);
|
|
}
|
|
|
|
/* set temperature if defined to a preliminary variable which may be used
|
|
in numparam evaluation */
|
|
if (temperature) {
|
|
temperature_value = atof(temperature);
|
|
cp_vset("pretemp", CP_REAL, &temperature_value);
|
|
}
|
|
if (ft_ngdebug) {
|
|
cp_getvar("pretemp", CP_REAL, &testemp);
|
|
printf("test temperature %f\n", testemp);
|
|
}
|
|
/* We are done handling the control stuff. Now process remainder of deck.
|
|
Go on if there is something left after the controls.*/
|
|
if (deck->li_next) {
|
|
fprintf(cp_out, "\nCircuit: %s\n\n", tt);
|
|
#ifdef HAS_PROGREP
|
|
SetAnalyse("Prepare Deck", 0);
|
|
#endif
|
|
/* Now expand subcircuit macros and substitute numparams.*/
|
|
if (!cp_getvar("nosubckt", CP_BOOL, NULL))
|
|
if ((deck->li_next = inp_subcktexpand(deck->li_next)) == NULL) {
|
|
line_free(realdeck, TRUE);
|
|
line_free(deck->li_actual, TRUE);
|
|
tfree(tt);
|
|
return;
|
|
}
|
|
|
|
/* Now handle translation of spice2c6 POLYs. */
|
|
#ifdef XSPICE
|
|
/* Translate all SPICE 2G6 polynomial type sources */
|
|
deck->li_next = ENHtranslate_poly(deck->li_next);
|
|
#endif
|
|
|
|
line_free(deck->li_actual, FALSE);
|
|
deck->li_actual = realdeck;
|
|
|
|
/* print out the expanded deck into debug-out2.txt */
|
|
if (ft_ngdebug) {
|
|
/*debug: print into file*/
|
|
FILE *fdo = fopen("debug-out2.txt", "w");
|
|
struct line *t = NULL;
|
|
fprintf(fdo, "**************** uncommented deck **************\n\n");
|
|
/* always print first line */
|
|
fprintf(fdo, "%6d %6d %s\n", deck->li_linenum_orig, deck->li_linenum, deck->li_line);
|
|
/* here without out-commented lines */
|
|
for (t = deck->li_next; t; t = t->li_next) {
|
|
if (*(t->li_line) == '*')
|
|
continue;
|
|
fprintf(fdo, "%6d %6d %s\n", t->li_linenum_orig, t->li_linenum, t->li_line);
|
|
}
|
|
fprintf(fdo, "\n****************** complete deck ***************\n\n");
|
|
/* now completely */
|
|
for (t = deck; t; t = t->li_next)
|
|
fprintf(fdo, "%6d %6d %s\n", t->li_linenum_orig, t->li_linenum, t->li_line);
|
|
fclose(fdo);
|
|
}
|
|
for (dd = deck; dd; dd = dd->li_next) {
|
|
/* get csparams and create vectors, being
|
|
available in .control section, in plot 'const' */
|
|
if (ciprefix(".csparam", dd->li_line)) {
|
|
wordlist *wlist = NULL;
|
|
wordlist *wl = NULL;
|
|
char *cstoken[3];
|
|
int i;
|
|
s = dd->li_line;
|
|
*s = '*';
|
|
s = dd->li_line + 8;
|
|
while (isspace(*s))
|
|
s++;
|
|
cstoken[0] = gettok_char(&s, '=', FALSE, FALSE);
|
|
cstoken[1] = gettok_char(&s, '=', TRUE, FALSE);
|
|
cstoken[2] = gettok(&s);
|
|
for (i = 0; i < 3; i++)
|
|
wl_append_word(&wlist, &wl, cstoken[i]);
|
|
com_let(wlist);
|
|
wl_free(wlist);
|
|
}
|
|
}
|
|
|
|
/* handle .if ... .elseif ... .else ... .endif statements. */
|
|
dotifeval(deck);
|
|
|
|
/*merge the two option line structs*/
|
|
if (!options && com_options)
|
|
options = com_options;
|
|
else if (options && com_options) {
|
|
/* move to end of options
|
|
struct line *tmp_options = options;
|
|
while (tmp_options) {
|
|
if (!tmp_options->li_next) break;
|
|
tmp_options = tmp_options->li_next;
|
|
}
|
|
tmp_options->li_next = com_options;*/
|
|
/* move to end of com_options */
|
|
struct line *tmp_options = com_options;
|
|
while (tmp_options) {
|
|
if (!tmp_options->li_next)
|
|
break;
|
|
tmp_options = tmp_options->li_next;
|
|
}
|
|
tmp_options->li_next = options;
|
|
}
|
|
|
|
/* prepare parse trees from 'temper' expressions */
|
|
if (expr_w_temper)
|
|
inp_parse_temper(deck);
|
|
|
|
/* If user wants all currents saved (.options savecurrents), add .save
|
|
to wl_first with all terminal currents available on selected devices */
|
|
inp_savecurrents(deck, options, &wl_first, controls);
|
|
|
|
/* now load deck into ft_curckt -- the current circuit. */
|
|
inp_dodeck(deck, tt, wl_first, FALSE, options, filename);
|
|
/* inp_dodeck did take ownership */
|
|
tt = NULL;
|
|
|
|
} /* if (deck->li_next) */
|
|
|
|
/* look for and set temperature; also store param and .meas statements in circuit struct */
|
|
if (ft_curckt) {
|
|
ft_curckt->ci_param = NULL;
|
|
ft_curckt->ci_meas = NULL;
|
|
/* PN add here stats*/
|
|
ft_curckt->FTEstats->FTESTATnetLoadTime = endTime - startTime;
|
|
}
|
|
|
|
for (dd = deck; dd; dd = dd->li_next) {
|
|
/* all parameter lines should be sequentially ordered and placed at
|
|
beginning of deck */
|
|
if (ciprefix(".param", dd->li_line)) {
|
|
ft_curckt->ci_param = dd;
|
|
/* find end of .param statements */
|
|
while (ciprefix(".param", dd->li_line)) {
|
|
prev_param = dd;
|
|
dd = dd->li_next;
|
|
if (dd == NULL)
|
|
break; // no line after .param line
|
|
}
|
|
prev_card->li_next = dd;
|
|
prev_param->li_next = NULL;
|
|
if (dd == NULL) {
|
|
fprintf(cp_err, "Warning: Missing .end card!\n");
|
|
break; // no line after .param line
|
|
}
|
|
}
|
|
|
|
if (ciprefix(".meas", dd->li_line)) {
|
|
if (cp_getvar("autostop", CP_BOOL, NULL)) {
|
|
if (strstr(dd->li_line, " max ") ||
|
|
strstr(dd->li_line, " min ") ||
|
|
strstr(dd->li_line, " avg ") ||
|
|
strstr(dd->li_line, " rms ") ||
|
|
strstr(dd->li_line, " integ "))
|
|
{
|
|
printf("Warning: .OPTION AUTOSTOP will not be effective because one of 'max|min|avg|rms|integ' is used in .meas\n");
|
|
printf(" AUTOSTOP being disabled...\n");
|
|
cp_remvar("autostop");
|
|
}
|
|
}
|
|
|
|
if (curr_meas == NULL) {
|
|
curr_meas = ft_curckt->ci_meas = dd;
|
|
} else {
|
|
curr_meas->li_next = dd;
|
|
curr_meas = dd;
|
|
}
|
|
prev_card->li_next = dd->li_next;
|
|
curr_meas->li_next = NULL;
|
|
dd = prev_card;
|
|
}
|
|
prev_card = dd;
|
|
} //end of for-loop
|
|
|
|
/* set temperature, if defined, to new value.
|
|
cp_vset will set the variable "temp" and also set CKTtemp,
|
|
so we can do it only here because the circuit has to be already there */
|
|
if (temperature) {
|
|
temperature_value = atof(temperature);
|
|
cp_vset("temp", CP_REAL, &temperature_value);
|
|
txfree(temperature);
|
|
}
|
|
|
|
#ifdef TRACE
|
|
/* SDB debug statement */
|
|
printf("In inp_spsource, done with dodeck.\n");
|
|
#endif
|
|
|
|
/* print out the expanded deck into debug-out3.txt */
|
|
if (ft_ngdebug) {
|
|
/*debug: print into file*/
|
|
FILE *fdo = fopen("debug-out3.txt", "w");
|
|
struct line *t = NULL;
|
|
fprintf(fdo, "**************** uncommented deck **************\n\n");
|
|
/* always print first line */
|
|
fprintf(fdo, "%6d %6d %s\n", deck->li_linenum_orig, deck->li_linenum, deck->li_line);
|
|
/* here without out-commented lines */
|
|
for (t = deck->li_next; t; t = t->li_next) {
|
|
if (*(t->li_line) == '*')
|
|
continue;
|
|
fprintf(fdo, "%6d %6d %s\n", t->li_linenum_orig, t->li_linenum, t->li_line);
|
|
}
|
|
fprintf(fdo, "\n****************** complete deck ***************\n\n");
|
|
/* now completely */
|
|
for (t = deck; t; t = t->li_next)
|
|
fprintf(fdo, "%6d %6d %s\n", t->li_linenum_orig, t->li_linenum, t->li_line);
|
|
fclose(fdo);
|
|
}
|
|
|
|
if (expr_w_temper) {
|
|
/* Now the circuit is defined, so generate the parse trees */
|
|
inp_parse_temper_trees();
|
|
/* Get the actual data for model and device instance parameters */
|
|
inp_evaluate_temper();
|
|
}
|
|
|
|
/* linked list dbs is used to store the "save" or .save data (defined in breakp2.c),
|
|
(When controls are executed later on, also stores TRACE, IPLOT, and STOP data) */
|
|
/* set to NULL to allow generation of a new dbs */
|
|
dbs = NULL;
|
|
/* .save data stored in dbs.
|
|
Do this here before controls are run: .save is thus recognized even if
|
|
.control is used */
|
|
ft_dotsaves();
|
|
|
|
/* Now that the deck is loaded, do the commands, if there are any */
|
|
controls = wl_reverse(controls);
|
|
for (wl = controls; wl; wl = wl->wl_next)
|
|
cp_evloop(wl->wl_word);
|
|
wl_free(controls);
|
|
}
|
|
|
|
/* Now reset everything. Pop the control stack, and fix up the IO
|
|
* as it was before the source. */
|
|
cp_popcontrol();
|
|
|
|
cp_curin = lastin;
|
|
cp_curout = lastout;
|
|
cp_curerr = lasterr;
|
|
|
|
tfree(tt);
|
|
}
|
|
|
|
|
|
/* This routine is cut in half here because com_rset has to do what
|
|
* follows also. End is the list of commands we execute when the job
|
|
* is finished: we only bother with this if we might be running in
|
|
* batch mode, since it isn't much use otherwise. */
|
|
/*------------------------------------------------------------------
|
|
* It appears that inp_dodeck adds the circuit described by *deck
|
|
* to the current circuit (ft_curckt).
|
|
*-----------------------------------------------------------------*/
|
|
void
|
|
inp_dodeck(
|
|
struct line *deck, /*in: the spice deck */
|
|
char *tt, /*in: the title of the deck */
|
|
wordlist *end, /*in: all lines with .width, .plot, .print, .save, .op, .meas, .tf */
|
|
bool reuse, /*in: TRUE if called from runcoms2.c com_rset,
|
|
FALSE if called from inp_spsource() */
|
|
struct line *options, /*in: all .option lines from deck */
|
|
char *filename /*in: input file of deck */
|
|
)
|
|
{
|
|
struct circ *ct;
|
|
struct line *dd;
|
|
CKTcircuit *ckt;
|
|
char *s;
|
|
INPtables *tab = NULL;
|
|
struct variable *eev = NULL;
|
|
wordlist *wl;
|
|
bool noparse, ii;
|
|
int print_listing;
|
|
bool have_err = FALSE;
|
|
int warn; /* whether SOA check should be performed */
|
|
int maxwarns = 0; /* specifies the maximum number of SOA warnings */
|
|
double startTime;
|
|
|
|
/* First throw away any old error messages there might be and fix
|
|
the case of the lines. */
|
|
for (dd = deck; dd; dd = dd->li_next)
|
|
if (dd->li_error) {
|
|
tfree(dd->li_error);
|
|
dd->li_error = NULL;
|
|
}
|
|
|
|
if (reuse) {
|
|
/* re-use existing circuit structure */
|
|
ct = ft_curckt;
|
|
} else {
|
|
if (ft_curckt) {
|
|
ft_curckt->ci_devices = cp_kwswitch(CT_DEVNAMES, NULL);
|
|
ft_curckt->ci_nodes = cp_kwswitch(CT_NODENAMES, NULL);
|
|
}
|
|
/* create new circuit structure */
|
|
ft_curckt = ct = alloc(struct circ);
|
|
|
|
/*PN FTESTATS*/
|
|
ft_curckt->FTEstats = TMALLOC(FTESTATistics, 1);
|
|
}
|
|
noparse = cp_getvar("noparse", CP_BOOL, NULL);
|
|
|
|
|
|
/* We check preliminary for the scale option. This special processing
|
|
is needed because we need the scale info BEFORE building the circuit
|
|
and seems there is no other way to do this. */
|
|
if (!noparse) {
|
|
struct line *opt_beg = options;
|
|
for (; options; options = options->li_next) {
|
|
for (s = options->li_line; *s && !isspace(*s); s++)
|
|
;
|
|
|
|
ii = cp_interactive;
|
|
cp_interactive = FALSE;
|
|
wl = cp_lexer(s);
|
|
cp_interactive = ii;
|
|
if (!wl || !wl->wl_word || !*wl->wl_word)
|
|
continue;
|
|
if (eev)
|
|
eev->va_next = cp_setparse(wl);
|
|
else
|
|
ct->ci_vars = eev = cp_setparse(wl);
|
|
wl_free(wl);
|
|
while (eev && (eev->va_next))
|
|
eev = eev->va_next;
|
|
}
|
|
for (eev = ct->ci_vars; eev; eev = eev->va_next) {
|
|
switch (eev->va_type) {
|
|
case CP_BOOL:
|
|
break;
|
|
case CP_NUM:
|
|
break;
|
|
case CP_REAL:
|
|
if (strcmp("scale", eev->va_name) == 0) {
|
|
cp_vset("scale", CP_REAL, &eev->va_real);
|
|
printf("Scale set\n");
|
|
}
|
|
break;
|
|
case CP_STRING:
|
|
break;
|
|
default: {
|
|
fprintf(stderr, "ERROR: enumeration value `CP_LIST' not handled in inp_dodeck\nAborting...\n");
|
|
controlled_exit(EXIT_FAILURE);
|
|
}
|
|
} /* switch . . . */
|
|
}
|
|
options = opt_beg; // back to the beginning
|
|
} /* if (!noparse) . . . */
|
|
|
|
/*----------------------------------------------------
|
|
* Now assuming that we wanna parse this deck, we call
|
|
* if_inpdeck which takes the deck and returns a
|
|
* a pointer to the circuit ckt.
|
|
*---------------------------------------------------*/
|
|
if (!noparse) {
|
|
startTime = seconds();
|
|
ckt = if_inpdeck(deck, &tab);
|
|
ft_curckt->FTEstats->FTESTATnetParseTime = seconds() - startTime;
|
|
} else {
|
|
ckt = NULL;
|
|
}
|
|
|
|
/* set ckt->CKTisLinear=1 if circuit only contains R, L, C */
|
|
if (ckt)
|
|
cktislinear(ckt, deck);
|
|
/* set some output terminal data */
|
|
out_init();
|
|
/* if_inpdeck() may return NULL upon error */
|
|
if (ckt) {
|
|
if (cp_getvar("warn", CP_NUM, &warn))
|
|
ckt->CKTsoaCheck = warn;
|
|
else
|
|
ckt->CKTsoaCheck = 0;
|
|
|
|
if (cp_getvar("maxwarns", CP_NUM, &maxwarns))
|
|
ckt->CKTsoaMaxWarns = maxwarns;
|
|
else
|
|
ckt->CKTsoaMaxWarns = 5;
|
|
}
|
|
|
|
ft_curckt->FTEstats->FTESTATdeckNumLines = 0;
|
|
/*----------------------------------------------------
|
|
Now run through the deck and look to see if there are
|
|
errors on any line (message contained in li_error).
|
|
|
|
Error messages have been generated either by writing
|
|
directly to ->li_error from a struct line or to
|
|
->error from a struct card , or by using one of the
|
|
macros as defined in inpmacs.h. Functions INPerror(),
|
|
INPerrCat(), and SPerror() are invoked.
|
|
*---------------------------------------------------*/
|
|
for (dd = deck; dd; dd = dd->li_next) {
|
|
|
|
ft_curckt->FTEstats->FTESTATdeckNumLines += 1;
|
|
|
|
#ifdef TRACE
|
|
/* SDB debug statement */
|
|
printf("In inp_dodeck, looking for errors and examining line %s . . . \n", dd->li_line);
|
|
#endif
|
|
|
|
if (dd->li_error) {
|
|
char *p, *q;
|
|
#ifdef XSPICE
|
|
/* add setting of ipc syntax error flag */
|
|
g_ipc.syntax_error = IPC_TRUE;
|
|
#endif
|
|
p = dd->li_error;
|
|
do {
|
|
q = strchr(p, '\n');
|
|
if (q)
|
|
*q = '\0';
|
|
|
|
if (p == dd->li_error) {
|
|
if (strstr(dd->li_line, ".model"))
|
|
out_printf("Warning: Model issue on line %d :\n %.*s ...\n%s\n",
|
|
dd->li_linenum_orig, 72, dd->li_line, dd->li_error);
|
|
else {
|
|
out_printf("Error on line %d :\n %s\n%s\n",
|
|
dd->li_linenum_orig, dd->li_line, dd->li_error);
|
|
have_err = TRUE;
|
|
}
|
|
if (ft_stricterror)
|
|
controlled_exit(EXIT_BAD);
|
|
} else {
|
|
out_printf("%s\n", p);
|
|
}
|
|
|
|
if (q)
|
|
*q++ = '\n';
|
|
p = q;
|
|
} while (p && *p);
|
|
} /* end if (dd->li_error) */
|
|
|
|
} /* for (dd = deck; dd; dd = dd->li_next) */
|
|
|
|
/* Stop here and exit if error occurred in batch mode */
|
|
if (have_err && ft_batchmode) {
|
|
fprintf(stderr, "\nngspice stopped due to error, no simulation run!\n");
|
|
controlled_exit(EXIT_BAD);
|
|
}
|
|
|
|
/* Only print out netlist if brief is FALSE */
|
|
if (!cp_getvar("brief", CP_BOOL, NULL)) {
|
|
/* output deck */
|
|
out_printf("\nProcessed Netlist\n");
|
|
out_printf("=================\n");
|
|
print_listing = 1;
|
|
for (dd = deck; dd; dd = dd->li_next) {
|
|
if (ciprefix(".prot", dd->li_line))
|
|
print_listing = 0;
|
|
if (print_listing == 1)
|
|
out_printf("%s\n", dd->li_line);
|
|
if (ciprefix(".unprot", dd->li_line))
|
|
print_listing = 1;
|
|
}
|
|
out_printf("\n");
|
|
}
|
|
|
|
/* Add this circuit to the circuit list. If reuse is TRUE
|
|
(command 'reset'), then use the existing ft_curckt structure. */
|
|
if (!reuse) {
|
|
/* Be sure that ci_devices and ci_nodes are valid */
|
|
ft_curckt->ci_devices = cp_kwswitch(CT_DEVNAMES, NULL);
|
|
cp_kwswitch(CT_DEVNAMES, ft_curckt->ci_devices);
|
|
ft_curckt->ci_nodes = cp_kwswitch(CT_NODENAMES, NULL);
|
|
cp_kwswitch(CT_NODENAMES, ft_curckt->ci_nodes);
|
|
ft_newcirc(ct);
|
|
/* Assign current circuit */
|
|
ft_curckt = ct;
|
|
}
|
|
ct->ci_name = tt;
|
|
ct->ci_deck = deck;
|
|
ct->ci_options = options;
|
|
if (deck->li_actual)
|
|
ct->ci_origdeck = deck->li_actual;
|
|
else
|
|
ct->ci_origdeck = ct->ci_deck;
|
|
ct->ci_ckt = ckt; /* attach the input ckt to the list of circuits */
|
|
ct->ci_symtab = tab;
|
|
ct->ci_inprogress = FALSE;
|
|
ct->ci_runonce = FALSE;
|
|
ct->ci_commands = end;
|
|
if (filename)
|
|
ct->ci_filename = copy(filename);
|
|
else
|
|
ct->ci_filename = NULL;
|
|
|
|
if (!noparse) {
|
|
/*
|
|
* for (; options; options = options->li_next) {
|
|
* for (s = options->li_line; *s && !isspace(*s); s++)
|
|
* ;
|
|
* ii = cp_interactive;
|
|
* cp_interactive = FALSE;
|
|
* wl = cp_lexer(s);
|
|
* cp_interactive = ii;
|
|
* if (!wl || !wl->wl_word || !*wl->wl_word)
|
|
* continue;
|
|
* if (eev)
|
|
* eev->va_next = cp_setparse(wl);
|
|
* else
|
|
* ct->ci_vars = eev = cp_setparse(wl);
|
|
* while (eev->va_next)
|
|
* eev = eev->va_next;
|
|
* }
|
|
*/
|
|
for (eev = ct->ci_vars; eev; eev = eev->va_next) {
|
|
bool one = TRUE; /* FIXME, actually eev->va_bool should be TRUE anyway */
|
|
switch (eev->va_type) {
|
|
case CP_BOOL:
|
|
if_option(ct->ci_ckt, eev->va_name, eev->va_type, &one);
|
|
break;
|
|
case CP_NUM:
|
|
if_option(ct->ci_ckt, eev->va_name, eev->va_type, &eev->va_num);
|
|
break;
|
|
case CP_REAL:
|
|
if_option(ct->ci_ckt, eev->va_name, eev->va_type, &eev->va_real);
|
|
break;
|
|
case CP_STRING:
|
|
if_option(ct->ci_ckt, eev->va_name, eev->va_type, eev->va_string);
|
|
break;
|
|
default: {
|
|
fprintf(stderr, "ERROR: enumeration value `CP_LIST' not handled in inp_dodeck\nAborting...\n");
|
|
controlled_exit(EXIT_FAILURE);
|
|
}
|
|
} // switch . . .
|
|
}
|
|
} // if (!noparse) . . .
|
|
|
|
/* add title of deck to data base */
|
|
/* this won't work if the title is the empty string
|
|
* cp_addkword() doesn't work for tt === ""
|
|
* since CT_CKTNAMES doesn't seem to be used anywhere
|
|
* I've disabled this piece.
|
|
*/
|
|
#if 0
|
|
cp_addkword(CT_CKTNAMES, tt);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Edit and re-load the current input deck. Note that if these
|
|
* commands are used on a non-unix machine, they will leave spice.tmp
|
|
* junk files lying around. */
|
|
void
|
|
com_edit(wordlist *wl)
|
|
{
|
|
char *filename;
|
|
FILE *fp;
|
|
bool inter, permfile;
|
|
char buf[BSIZE_SP];
|
|
|
|
inter = cp_interactive;
|
|
cp_interactive = FALSE;
|
|
if (wl) {
|
|
if (!doedit(wl->wl_word)) {
|
|
cp_interactive = inter;
|
|
return;
|
|
}
|
|
if ((fp = inp_pathopen(wl->wl_word, "r")) == NULL) {
|
|
perror(wl->wl_word);
|
|
cp_interactive = inter;
|
|
return;
|
|
}
|
|
inp_spsource(fp, FALSE, wl->wl_word, FALSE);
|
|
} else {
|
|
/* If there is no circuit yet, then create one */
|
|
if (ft_curckt && ft_curckt->ci_filename) {
|
|
filename = ft_curckt->ci_filename;
|
|
permfile = TRUE;
|
|
} else {
|
|
filename = smktemp("sp");
|
|
permfile = FALSE;
|
|
}
|
|
if (ft_curckt && !ft_curckt->ci_filename) {
|
|
if ((fp = fopen(filename, "w")) == NULL) {
|
|
perror(filename);
|
|
cp_interactive = inter;
|
|
return;
|
|
}
|
|
inp_list(fp, ft_curckt->ci_deck, ft_curckt->ci_options, LS_DECK);
|
|
fprintf(cp_err,
|
|
"Warning: editing a temporary file -- "
|
|
"circuit not saved\n");
|
|
fclose(fp);
|
|
} else if (!ft_curckt) {
|
|
if ((fp = fopen(filename, "w")) == NULL) {
|
|
perror(filename);
|
|
cp_interactive = inter;
|
|
return;
|
|
}
|
|
fprintf(fp, "SPICE 3 test deck\n");
|
|
fclose(fp);
|
|
}
|
|
if (!doedit(filename)) {
|
|
cp_interactive = inter;
|
|
return;
|
|
}
|
|
|
|
if ((fp = fopen(filename, "r")) == NULL) {
|
|
perror(filename);
|
|
cp_interactive = inter;
|
|
return;
|
|
}
|
|
inp_spsource(fp, FALSE, permfile ? filename : NULL, FALSE);
|
|
|
|
/* fclose(fp); */
|
|
/* MW. inp_spsource already closed fp */
|
|
|
|
if (ft_curckt && !ft_curckt->ci_filename)
|
|
unlink(filename);
|
|
}
|
|
|
|
cp_interactive = inter;
|
|
|
|
/* note: default is to run circuit after successful edit */
|
|
|
|
fprintf(cp_out, "run circuit? ");
|
|
fflush(cp_out);
|
|
fgets(buf, BSIZE_SP, stdin);
|
|
if (buf[0] != 'n') {
|
|
fprintf(cp_out, "running circuit\n");
|
|
com_run(NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static bool
|
|
doedit(char *filename)
|
|
{
|
|
char buf[BSIZE_SP], buf2[BSIZE_SP], *editor;
|
|
|
|
if (cp_getvar("editor", CP_STRING, buf2)) {
|
|
editor = buf2;
|
|
} else {
|
|
if ((editor = getenv("EDITOR")) == NULL) {
|
|
if (Def_Editor && *Def_Editor)
|
|
editor = Def_Editor;
|
|
else
|
|
editor = "/usr/bin/vi";
|
|
}
|
|
}
|
|
sprintf(buf, "%s %s", editor, filename);
|
|
return (system(buf) ? FALSE : TRUE);
|
|
}
|
|
|
|
|
|
void
|
|
com_source(wordlist *wl)
|
|
{
|
|
FILE *fp, *tp;
|
|
char buf[BSIZE_SP];
|
|
bool inter;
|
|
char *tempfile = NULL, *firstfile;
|
|
|
|
wordlist *owl = wl;
|
|
size_t n;
|
|
|
|
inter = cp_interactive;
|
|
cp_interactive = FALSE;
|
|
|
|
firstfile = wl->wl_word;
|
|
|
|
if (wl->wl_next) {
|
|
/* There are several files -- put them into a temp file */
|
|
tempfile = smktemp("sp");
|
|
if ((fp = inp_pathopen(tempfile, "w+")) == NULL) {
|
|
perror(tempfile);
|
|
cp_interactive = TRUE;
|
|
return;
|
|
}
|
|
while (wl) {
|
|
if ((tp = inp_pathopen(wl->wl_word, "r")) == NULL) {
|
|
perror(wl->wl_word);
|
|
fclose(fp);
|
|
cp_interactive = TRUE;
|
|
unlink(tempfile);
|
|
return;
|
|
}
|
|
while ((n = fread(buf, 1, BSIZE_SP, tp)) > 0)
|
|
fwrite(buf, 1, n, fp);
|
|
fclose(tp);
|
|
wl = wl->wl_next;
|
|
}
|
|
fseek(fp, 0L, SEEK_SET);
|
|
} else {
|
|
fp = inp_pathopen(wl->wl_word, "r");
|
|
}
|
|
|
|
if (fp == NULL) {
|
|
perror(wl->wl_word);
|
|
cp_interactive = TRUE;
|
|
return;
|
|
}
|
|
|
|
/* Don't print the title if this is a spice initialisation file. */
|
|
if (ft_nutmeg || substring(INITSTR, owl->wl_word) || substring(ALT_INITSTR, owl->wl_word))
|
|
inp_spsource(fp, TRUE, tempfile ? NULL : wl->wl_word, FALSE);
|
|
else {
|
|
/* Save path name for use in XSPICE fopen_with_path() */
|
|
if (Infile_Path)
|
|
tfree(Infile_Path);
|
|
Infile_Path = ngdirname(firstfile);
|
|
inp_spsource(fp, FALSE, tempfile ? NULL : wl->wl_word, FALSE);
|
|
}
|
|
|
|
cp_interactive = inter;
|
|
if (tempfile)
|
|
unlink(tempfile);
|
|
}
|
|
|
|
|
|
void
|
|
inp_source(char *file)
|
|
{
|
|
static struct wordlist wl = { NULL, NULL, NULL };
|
|
wl.wl_word = file;
|
|
com_source(&wl);
|
|
}
|
|
|
|
|
|
/* check the input deck (after inpcom and numparam extensions)
|
|
for linear elements. If only linear elements are found,
|
|
ckt->CKTisLinear is set to 1. Return immediately if a first
|
|
non-linear element is found. */
|
|
static void
|
|
cktislinear(CKTcircuit *ckt, struct line *deck)
|
|
{
|
|
struct line *dd;
|
|
char firstchar;
|
|
|
|
if (deck->li_next)
|
|
for (dd = deck->li_next; dd; dd = dd->li_next) {
|
|
firstchar = *dd->li_line;
|
|
switch (firstchar) {
|
|
case 'r':
|
|
case 'l':
|
|
case 'c':
|
|
case 'i':
|
|
case 'v':
|
|
case '*':
|
|
case '.':
|
|
case 'e':
|
|
case 'g':
|
|
case 'f':
|
|
case 'h':
|
|
continue;
|
|
break;
|
|
default:
|
|
ckt->CKTisLinear = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ckt->CKTisLinear = 1;
|
|
}
|
|
|
|
|
|
/* global array for assembling circuit lines entered by fcn circbyline
|
|
or receiving array from external caller. Array is created once per ngspice call.
|
|
Last line of the array has to get the value NULL */
|
|
char **circarray;
|
|
|
|
|
|
void
|
|
create_circbyline(char *line)
|
|
{
|
|
static int linec = 0;
|
|
static int memlen = 256;
|
|
FILE *fp = NULL;
|
|
if (!circarray)
|
|
circarray = TMALLOC(char*, memlen);
|
|
circarray[linec++] = line;
|
|
if (linec < memlen) {
|
|
if (ciprefix(".end", line) && (line[4] == '\0' || isspace(line[4]))) {
|
|
circarray[linec] = NULL;
|
|
inp_spsource(fp, FALSE, "", TRUE);
|
|
linec = 0;
|
|
}
|
|
}
|
|
else {
|
|
memlen += memlen;
|
|
circarray = TREALLOC(char*, circarray, memlen);
|
|
}
|
|
}
|
|
|
|
|
|
/* fcn called by command 'circbyline' */
|
|
void
|
|
com_circbyline(wordlist *wl)
|
|
{
|
|
/* undo the automatic wordline creation.
|
|
wl_flatten allocates memory on the heap for each newline.
|
|
This memory will be released line by line in inp_source(). */
|
|
|
|
char *newline = wl_flatten(wl);
|
|
create_circbyline(newline);
|
|
}
|
|
|
|
|
|
/* handle .if('expr') ... .elseif('expr') ... .else ... .endif statements.
|
|
numparam has evaluated .if('boolean expression') to
|
|
.if ( 1.000000000e+000 ) or .elseif ( 0.000000000e+000 ) */
|
|
static void
|
|
dotifeval(struct line *deck)
|
|
{
|
|
int iftrue = 0, elseiftrue = 0, elsetrue = 0, iffound = 0, elseiffound = 0, elsefound = 0;
|
|
struct line *dd;
|
|
char *dottoken;
|
|
char *s, *t;
|
|
|
|
/* skip the first line (title line) */
|
|
for (dd = deck->li_next; dd; dd = dd->li_next) {
|
|
|
|
s = t = dd->li_line;
|
|
|
|
if (*s == '*')
|
|
continue;
|
|
|
|
dottoken = gettok(&t);
|
|
/* find '.if' and read its parameter */
|
|
if (cieq(dottoken, ".if")) {
|
|
elsefound = 0;
|
|
elseiffound = 0;
|
|
iffound = 1;
|
|
*s = '*';
|
|
s = dd->li_line + 3;
|
|
iftrue = atoi(s);
|
|
}
|
|
else if (cieq(dottoken, ".elseif")) {
|
|
elsefound = 0;
|
|
elseiffound = 1;
|
|
iffound = 0;
|
|
*s = '*';
|
|
if (!iftrue) {
|
|
s = dd->li_line + 7;
|
|
elseiftrue = atoi(s);
|
|
}
|
|
}
|
|
else if (cieq(dottoken, ".else")) {
|
|
elsefound = 1;
|
|
elseiffound = 0;
|
|
iffound = 0;
|
|
if (!iftrue && !elseiftrue)
|
|
elsetrue = 1;
|
|
*s = '*';
|
|
}
|
|
else if (cieq(dottoken, ".endif")) {
|
|
elsefound = elseiffound = iffound = 0;
|
|
elsetrue = elseiftrue = iftrue = 0;
|
|
*s = '*';
|
|
// inp_subcktexpand(dd);
|
|
}
|
|
else {
|
|
if (iffound && !iftrue) {
|
|
*s = '*';
|
|
}
|
|
else if (elseiffound && !elseiftrue) {
|
|
*s = '*';
|
|
}
|
|
else if (elsefound && !elsetrue) {
|
|
*s = '*';
|
|
}
|
|
}
|
|
tfree(dottoken);
|
|
}
|
|
}
|
|
|
|
|
|
/* List of all expressions found in .model lines */
|
|
static struct pt_temper *modtlist = NULL;
|
|
|
|
/* List of all expressions found in device instance lines */
|
|
static struct pt_temper *devtlist = NULL;
|
|
|
|
/*
|
|
Evaluate expressions containing 'temper' keyword, found in
|
|
.model lines or device instance lines.
|
|
Activity has four steps:
|
|
1) Prepare the expressions to survive numparam expansion
|
|
(see function inp_temper_compat() in inpcom.c). A global
|
|
variable expr_w_temper is set TRUE if any expression with
|
|
'temper' has been found.
|
|
2) After numparam insertion and subcircuit expansion,
|
|
get the expressions, store them with a place holder for the
|
|
pointer to the expression parse tree and a wordlist containing
|
|
device/model name, parameter name and a placeholder for the
|
|
evaluation result ready to be used by com_alter(mod) functions,
|
|
in linked lists modtlist (model) or devtlist (device instance).
|
|
(done function inp_parse_temper()).
|
|
3) After the circuit structure has been established, generate
|
|
the parse trees. We can do it only then because pointers to
|
|
ckt->CKTtemp and others are stored in the trees.
|
|
(done in function inp_parse_temper_trees()).
|
|
4) Evaluation of the parse trees is requested by calling function
|
|
inp_evaluate_temper(). The B Source parser is invoked here.
|
|
ckt->CKTtemp is used to replace the 'temper' token by the actual
|
|
circuit temperature. The evaluation results are added to the
|
|
wordlist, com_alter(mod) is called to set the new parameters
|
|
to the model parameters or device instance parameters.
|
|
*/
|
|
|
|
static int
|
|
inp_parse_temper(struct line *card)
|
|
{
|
|
int error = 0;
|
|
char *end_tstr, *beg_tstr, *beg_pstr, *str_ptr, *devmodname, *paramname;
|
|
|
|
/* skip title line */
|
|
card = card->li_next;
|
|
for (; card; card = card->li_next) {
|
|
|
|
char *curr_line = card->li_line;
|
|
|
|
/* exclude some elements */
|
|
if ((*curr_line == '*') || (*curr_line == 'v') || (*curr_line == 'b') || (*curr_line == 'i') ||
|
|
(*curr_line == 'e') || (*curr_line == 'g') || (*curr_line == 'f') || (*curr_line == 'h'))
|
|
continue;
|
|
/* exclude all dot commands except .model */
|
|
if ((*curr_line == '.') && (!prefix(".model", curr_line)))
|
|
continue;
|
|
/* exclude lines not containing 'temper' */
|
|
if (strstr(curr_line, "temper") == NULL)
|
|
continue;
|
|
/* now start processing of the remaining lines containing 'temper' */
|
|
if (prefix(".model", curr_line)) {
|
|
struct pt_temper *modtlistnew = NULL;
|
|
/* remove '.model' */
|
|
str_ptr = gettok(&curr_line);
|
|
tfree(str_ptr);
|
|
devmodname = gettok(&curr_line);
|
|
beg_tstr = curr_line;
|
|
while ((end_tstr = beg_tstr = strstr(beg_tstr, "temper")) != NULL) {
|
|
wordlist *wl = NULL, *wlend = NULL;
|
|
modtlistnew = TMALLOC(struct pt_temper, 1);
|
|
while ((*beg_tstr) != '=')
|
|
beg_tstr--;
|
|
beg_pstr = beg_tstr;
|
|
/* go back over param name */
|
|
while(isspace(*beg_pstr))
|
|
beg_pstr--;
|
|
while(!isspace(*beg_pstr))
|
|
beg_pstr--;
|
|
/* get parameter name */
|
|
paramname = copy_substring(beg_pstr + 1, beg_tstr);
|
|
/* find end of expression string */
|
|
while (((*end_tstr) != '\0') && ((*end_tstr) != '='))
|
|
end_tstr++;
|
|
/* go back over next param name */
|
|
if (*end_tstr == '=') {
|
|
end_tstr--;
|
|
while(isspace(*end_tstr))
|
|
end_tstr--;
|
|
while(!isspace(*end_tstr))
|
|
end_tstr--;
|
|
}
|
|
/* copy the expression */
|
|
modtlistnew->expression = copy_substring(beg_tstr + 1, end_tstr);
|
|
/* now remove this parameter entry by overwriting with ' '
|
|
ngspice then will use the default parameter to set up the circuit */
|
|
for (str_ptr = beg_pstr; str_ptr < end_tstr; str_ptr++)
|
|
*str_ptr = ' ';
|
|
|
|
modtlistnew->next = NULL;
|
|
/* create wordlist suitable for com_altermod */
|
|
wl_append_word(&wl, &wlend, devmodname);
|
|
wl_append_word(&wl, &wlend, paramname);
|
|
wl_append_word(&wl, &wlend, copy("="));
|
|
/* to be filled in by evaluation function */
|
|
wl_append_word(&wl, &wlend, NULL);
|
|
modtlistnew->wl = wl;
|
|
modtlistnew->wlend = wlend;
|
|
|
|
/* fill in the linked parse tree list */
|
|
if (modtlist) {
|
|
struct pt_temper *modtlisttmp = modtlist;
|
|
modtlist = modtlistnew;
|
|
modtlist->next = modtlisttmp;
|
|
} else {
|
|
modtlist = modtlistnew;
|
|
}
|
|
}
|
|
} else { /* instance expression with 'temper' */
|
|
struct pt_temper *devtlistnew = NULL;
|
|
/* get device name */
|
|
devmodname = gettok(&curr_line);
|
|
beg_tstr = curr_line;
|
|
while ((end_tstr = beg_tstr = strstr(beg_tstr, "temper")) != NULL) {
|
|
wordlist *wl = NULL, *wlend = NULL;
|
|
devtlistnew = TMALLOC(struct pt_temper, 1);
|
|
while ((*beg_tstr) != '=')
|
|
beg_tstr--;
|
|
beg_pstr = beg_tstr;
|
|
/* go back over param name */
|
|
while(isspace(*beg_pstr))
|
|
beg_pstr--;
|
|
while(!isspace(*beg_pstr))
|
|
beg_pstr--;
|
|
/* get parameter name */
|
|
paramname = copy_substring(beg_pstr + 1, beg_tstr);
|
|
/* find end of expression string */
|
|
while (((*end_tstr) != '\0') && ((*end_tstr) != '='))
|
|
end_tstr++;
|
|
/* go back over next param name */
|
|
if (*end_tstr == '=') {
|
|
end_tstr--;
|
|
while(isspace(*end_tstr))
|
|
end_tstr--;
|
|
while(!isspace(*end_tstr))
|
|
end_tstr--;
|
|
}
|
|
/* copy the expression */
|
|
devtlistnew->expression = copy_substring(beg_tstr + 1, end_tstr);
|
|
/* now remove this parameter entry by overwriting with ' '
|
|
ngspice then will use the default parameter to set up the circuit */
|
|
for (str_ptr = beg_pstr; str_ptr < end_tstr; str_ptr++)
|
|
*str_ptr = ' ';
|
|
|
|
devtlistnew->next = NULL;
|
|
/* create wordlist suitable for com_altermod */
|
|
wl_append_word(&wl, &wlend, devmodname);
|
|
wl_append_word(&wl, &wlend, paramname);
|
|
wl_append_word(&wl, &wlend, copy("="));
|
|
/* to be filled in by evaluation function */
|
|
wl_append_word(&wl, &wlend, NULL);
|
|
devtlistnew->wl = wl;
|
|
devtlistnew->wlend = wlend;
|
|
|
|
/* fill in the linked parse tree list */
|
|
if (devtlist) {
|
|
struct pt_temper *devtlisttmp = devtlist;
|
|
devtlist = devtlistnew;
|
|
devtlist->next = devtlisttmp;
|
|
} else {
|
|
devtlist = devtlistnew;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
static void
|
|
inp_parse_temper_trees(void)
|
|
{
|
|
struct pt_temper *d;
|
|
|
|
for(d = devtlist; d; d = d->next)
|
|
INPgetTree(&d->expression, &d->pt, ft_curckt->ci_ckt, NULL);
|
|
|
|
for(d = modtlist; d; d = d->next)
|
|
INPgetTree(&d->expression, &d->pt, ft_curckt->ci_ckt, NULL);
|
|
}
|
|
|
|
|
|
void
|
|
inp_evaluate_temper(void)
|
|
{
|
|
struct pt_temper *d;
|
|
double result;
|
|
char fts[128];
|
|
|
|
for(d = devtlist; d; d = d->next) {
|
|
IFeval((IFparseTree *) d->pt, 1e-12, &result, NULL, NULL);
|
|
sprintf(fts, "%g", result);
|
|
d->wlend->wl_word = copy(fts);
|
|
com_alter(d->wl);
|
|
}
|
|
|
|
for(d = modtlist; d; d = d->next) {
|
|
/* only evaluate models which have been entered into the
|
|
hash table ckt->MODnameHash */
|
|
if (ft_sim->findModel (ft_curckt->ci_ckt, d->wl->wl_word) == NULL)
|
|
continue;
|
|
IFeval((IFparseTree *) d->pt, 1e-12, &result, NULL, NULL);
|
|
sprintf(fts, "%g", result);
|
|
d->wlend->wl_word = copy(fts);
|
|
com_altermod(d->wl);
|
|
}
|
|
}
|
|
|
|
|
|
/* Enable current measurements by the user. Check, if option savecurrents
|
|
is set by the user. We have to do it here prematurely, because options
|
|
will be processed later.
|
|
Then check, if commands 'save' or '.save' are alraedy there. If not, add
|
|
'.save all'.
|
|
Then the deck is scanned for known devices, and their current vectors in form of
|
|
@q1[ib] are added to new .save lines in wl_first. */
|
|
static void inp_savecurrents(struct line *deck, struct line *options, wordlist **wl, wordlist *con)
|
|
{
|
|
struct line *tmp_deck, *tmp_line;
|
|
char beg;
|
|
char *devname, *devline, *newline;
|
|
bool goon = FALSE, havesave = FALSE;
|
|
wordlist *tmpword;
|
|
|
|
/* check if option 'savecurrents' is set */
|
|
for (tmp_line = options; tmp_line; tmp_line = tmp_line->li_next)
|
|
if (strstr(tmp_line->li_line, "savecurrents")) {
|
|
goon = TRUE;
|
|
break;
|
|
}
|
|
if (!goon)
|
|
return;
|
|
/* check if we have a 'save' command in the .control section */
|
|
for (tmpword = con; tmpword; tmpword = tmpword->wl_next)
|
|
if(prefix("save", tmpword->wl_word)) {
|
|
havesave = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* check if wl_first is already there */
|
|
if (*wl) {
|
|
/* check if .save is already in wl_first */
|
|
for (tmpword = *wl; tmpword; tmpword = tmpword->wl_next)
|
|
if(prefix(".save", tmpword->wl_word)) {
|
|
havesave = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if we neither have 'save' nor '.save', add '.save all'
|
|
or if we do not have wl_first, add at least a wordline '*' to allow wl_append_word() */
|
|
if (!(*wl) || !havesave) {
|
|
*wl = alloc(wordlist);
|
|
(*wl)->wl_next = NULL;
|
|
(*wl)->wl_prev = NULL;
|
|
if (havesave)
|
|
(*wl)->wl_word = copy("*");
|
|
else
|
|
(*wl)->wl_word = copy(".save all");
|
|
}
|
|
/* Scan the deck for devices with their terminals.
|
|
We currently serve bipolars, resistors, MOS1, capacitors, inductors,
|
|
controlled current sources. Others may follow. */
|
|
for (tmp_deck = deck->li_next; tmp_deck; tmp_deck = tmp_deck->li_next){
|
|
beg = *(tmp_deck->li_line);
|
|
if ((beg == '*') || (beg == '.'))
|
|
continue;
|
|
switch (beg) {
|
|
case 'm':
|
|
devline = tmp_deck->li_line;
|
|
devname = gettok(&devline);
|
|
/* .save @q1[id] @q1[is] @q1[ig] @q1[ib] */
|
|
newline = tprintf(".save @%s[id] @%s[is] @%s[ig] @%s[ib]",
|
|
devname, devname, devname, devname);
|
|
wl_append_word(NULL, wl, newline);
|
|
break;
|
|
case 'j':
|
|
devline = tmp_deck->li_line;
|
|
devname = gettok(&devline);
|
|
/* .save @q1[id] @q1[is] @q1[ig] @q1[igd] */
|
|
newline = tprintf(".save @%s[id] @%s[is] @%s[ig] @%s[igd]",
|
|
devname, devname, devname, devname);
|
|
wl_append_word(NULL, wl, newline);
|
|
break;
|
|
case 'q':
|
|
devline = tmp_deck->li_line;
|
|
devname = gettok(&devline);
|
|
/* .save @q1[ic] @q1[ie] @q1[ib] @q1[is] */
|
|
newline = tprintf(".save @%s[ic] @%s[ie] @%s[ib] @%s[is]",
|
|
devname, devname, devname, devname);
|
|
wl_append_word(NULL, wl, newline);
|
|
break;
|
|
case 'd':
|
|
devline = tmp_deck->li_line;
|
|
devname = gettok(&devline);
|
|
/* .save @d1[id] */
|
|
newline = tprintf(".save @%s[id]", devname);
|
|
wl_append_word(NULL, wl, newline);
|
|
break;
|
|
case 'r':
|
|
case 'c':
|
|
case 'l':
|
|
case 'b':
|
|
case 'f':
|
|
case 'g':
|
|
case 'w':
|
|
case 's':
|
|
devline = tmp_deck->li_line;
|
|
devname = gettok(&devline);
|
|
/* .save @r1[i] */
|
|
newline = tprintf(".save @%s[i]", devname);
|
|
wl_append_word(NULL, wl, newline);
|
|
break;
|
|
case 'i':
|
|
devline = tmp_deck->li_line;
|
|
devname = gettok(&devline);
|
|
/* .save @i1[current] */
|
|
newline = tprintf(".save @%s[current]", devname);
|
|
wl_append_word(NULL, wl, newline);
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
while((*wl)->wl_prev)
|
|
(*wl) = (*wl)->wl_prev;
|
|
}
|