ngspice/src/frontend/inpcompat.c

1911 lines
70 KiB
C

/**********
Copyright 2023 The ngspice team. All rights reserved.
License: Three-clause BCD
Author: 2023 Holger Vogt
**********/
/*
For dealing with compatibility transformations
PSPICE, LTSPICE and others
*/
#include "ngspice/ngspice.h"
#include "ngspice/compatmode.h"
#include "ngspice/cpdefs.h"
#include "ngspice/dstring.h"
#include "ngspice/dvec.h"
#include "ngspice/ftedefs.h"
#include "ngspice/fteext.h"
#include "ngspice/fteinp.h"
#include "numparam/general.h"
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#if !defined(__MINGW32__) && !defined(_MSC_VER)
#include <unistd.h>
#endif
#include "../misc/util.h" /* ngdirname() */
#include "inpcom.h"
#include "ngspice/stringskip.h"
#include "ngspice/stringutil.h"
#include "ngspice/wordlist.h"
#include "subckt.h"
#include "variable.h"
#define INTEGRATE_UDEVICES
#ifdef INTEGRATE_UDEVICES
#include "ngspice/udevices.h"
#endif
void print_compat_mode(void);
void set_compat_mode(void);
struct card* pspice_compat(struct card* newcard);
void pspice_compat_a(struct card* oldcard);
struct card* ltspice_compat(struct card* oldcard);
void ltspice_compat_a(struct card* oldcard);
/* Set a compatibility flag.
Currently available are flags for:
- LTSPICE, HSPICE, Spice3, PSPICE, KiCad, Spectre, XSPICE
*/
struct compat newcompat;
void set_compat_mode(void)
{
char behaviour[80];
newcompat.hs = FALSE;
newcompat.ps = FALSE;
newcompat.xs = FALSE;
newcompat.lt = FALSE;
newcompat.ki = FALSE;
newcompat.a = FALSE;
newcompat.spe = FALSE;
newcompat.isset = FALSE;
newcompat.s3 = FALSE;
newcompat.mc = FALSE;
if (cp_getvar("ngbehavior", CP_STRING, behaviour, sizeof(behaviour))) {
if (strstr(behaviour, "hs"))
newcompat.isset = newcompat.hs = TRUE; /*HSPICE*/
if (strstr(behaviour, "ps"))
newcompat.isset = newcompat.ps = TRUE; /*PSPICE*/
if (strstr(behaviour, "xs"))
newcompat.isset = newcompat.xs = TRUE; /*XSPICE*/
if (strstr(behaviour, "lt"))
newcompat.isset = newcompat.lt = TRUE; /*LTSPICE*/
if (strstr(behaviour, "ki"))
newcompat.isset = newcompat.ki = TRUE; /*KiCad*/
if (strstr(behaviour, "a"))
newcompat.isset = newcompat.a = TRUE; /*complete netlist, used in conjuntion with other mode*/
if (strstr(behaviour, "ll"))
newcompat.isset = newcompat.ll = TRUE; /*all (currently not used)*/
if (strstr(behaviour, "s3"))
newcompat.isset = newcompat.s3 = TRUE; /*spice3 only*/
if (strstr(behaviour, "eg"))
newcompat.isset = newcompat.eg = TRUE; /*EAGLE*/
if (strstr(behaviour, "spe")) {
newcompat.isset = newcompat.spe = TRUE; /*Spectre*/
newcompat.ps = newcompat.lt = newcompat.ki = newcompat.eg = FALSE;
}
if (strstr(behaviour, "mc")) {
newcompat.isset = FALSE;
newcompat.mc = TRUE; /*make check*/
}
}
if (newcompat.hs && newcompat.ps) {
fprintf(stderr, "Warning: hs and ps compatibility are mutually exclusive, switch to ps!\n");
newcompat.hs = FALSE;
}
/* reset everything for 'make check' */
if (newcompat.mc)
newcompat.eg = newcompat.hs = newcompat.spe = newcompat.ps = newcompat.xs =
newcompat.ll = newcompat.lt = newcompat.ki = newcompat.a = FALSE;
}
/* Print the compatibility flags */
void print_compat_mode(void) {
if (newcompat.mc) /* make check */
return;
if (newcompat.isset) {
fprintf(stdout, "\n");
fprintf(stdout, "Note: Compatibility modes selected:");
if (newcompat.hs)
fprintf(stdout, " hs");
if (newcompat.ps)
fprintf(stdout, " ps");
if (newcompat.xs)
fprintf(stdout, " xs");
if (newcompat.lt)
fprintf(stdout, " lt");
if (newcompat.ki)
fprintf(stdout, " ki");
if (newcompat.ll)
fprintf(stdout, " ll");
if (newcompat.s3)
fprintf(stdout, " s3");
if (newcompat.eg)
fprintf(stdout, " eg");
if (newcompat.spe)
fprintf(stdout, " spe");
if (newcompat.a)
fprintf(stdout, " a");
fprintf(stdout, "\n\n");
}
else {
fprintf(stdout, "\n");
fprintf(stdout, "Note: No compatibility mode selected!\n\n");
}
}
/* replace the E and G source TABLE function by a B source pwl
* (used by ST OpAmps and comparators of Infineon models).
* E_RO_3 VB_3 VB_4 VALUE={ TABLE( V(VCCP,VCCN), 2 , 35 , 3.3 , 15 , 5 , 10
* )*I(VreadIo)}
* will become
* BE_RO_3_1 TABLE_NEW_1 0 v = pwl( V(VCCP,VCCN), 2 , 35 , 3.3 , 15 , 5 , 10)
* E_RO_3 VB_3 VB_4 VALUE={ V(TABLE_NEW_1)*I(VreadIo)}
*/
static void replace_table(struct card *startcard)
{
struct card *card;
static int numb = 0;
for (card = startcard; card; card = card->nextcard) {
char *cut_line = card->line;
if (*cut_line == 'e' || *cut_line == 'g') {
char *valp = search_plain_identifier(cut_line, "value");
char *valp2 = search_plain_identifier(cut_line, "cur");
if (valp || (valp2 && *cut_line == 'g')) {
char *ftablebeg = strstr(cut_line, "table(");
while (ftablebeg) {
/* get the beginning of the line */
char *begline = copy_substring(cut_line, ftablebeg);
/* get the table function */
char *tabfun = gettok_char(&ftablebeg, ')', TRUE, TRUE);
/* the new e, g line */
char *neweline = tprintf("%s v(table_new_%d)%s",
begline, numb, ftablebeg);
char *newbline =
tprintf("btable_new_%d table_new_%d 0 v=pwl%s",
numb, numb, tabfun + 5);
numb++;
tfree(tabfun);
tfree(begline);
tfree(card->line);
card->line = cut_line = neweline;
insert_new_line(card, newbline, 0, card->linenum_orig, card->linesource);
/* read next TABLE function in cut_line */
ftablebeg = strstr(cut_line, "table(");
}
continue;
}
}
}
}
/* find the model requested by ako:model and do the replacement */
static struct card *find_model(struct card *startcard,
struct card *changecard, char *searchname, char *newmname,
char *newmtype, char *endstr)
{
struct card *nomod, *returncard = changecard;
char *origmname, *origmtype;
char *beginline = startcard->line;
if (ciprefix(".subckt", beginline))
startcard = startcard->nextcard;
int nesting2 = 0;
for (nomod = startcard; nomod; nomod = nomod->nextcard) {
char *origmodline = nomod->line;
if (ciprefix(".subckt", origmodline))
nesting2++;
if (ciprefix(".ends", origmodline))
nesting2--;
/* skip any subcircuit */
if (nesting2 > 0)
continue;
if (nesting2 == -1) {
returncard = changecard;
break;
}
if (ciprefix(".model", origmodline)) {
origmodline = nexttok(origmodline);
origmname = gettok(&origmodline);
origmtype = gettok_noparens(&origmodline);
if (cieq(origmname, searchname)) {
if (!eq(origmtype, newmtype)) {
fprintf(stderr,
"Error: Original (%s) and new (%s) type for AKO "
"model disagree\n",
origmtype, newmtype);
controlled_exit(1);
}
/* we have got it */
char *newmodcard = tprintf(".model %s %s %s%s",
newmname, newmtype, origmodline, endstr);
char *tmpstr = strstr(newmodcard, ")(");
if (tmpstr) {
tmpstr[0] = ' ';
tmpstr[1] = ' ';
}
tfree(changecard->line);
changecard->line = newmodcard;
tfree(origmname);
tfree(origmtype);
returncard = NULL;
break;
}
tfree(origmname);
tfree(origmtype);
}
else
returncard = changecard;
}
return returncard;
}
/* Process any .distribution cards for PSPICE's Monte-Carlo feature.
* A .distribution card defines a probability distribution by a PWL
* density function. This could be rewritten as a function that
* returns a random value following that distribution.
* For now, just comment it away.
*/
static void do_distribution(struct card *oldcard) {
while (oldcard) {
char *line = oldcard->line;
if (line && ciprefix(".distribution", line))
*line = '*';
oldcard = oldcard->nextcard;
}
}
/* Do the .model replacement required by ako (a kind of)
* PSPICE does not support nested .subckt definitions, so
* a simple structure is needed: search for ako:modelname,
* then for modelname in the subcircuit or in the top level.
* .model qorig npn (BF=48 IS=2e-7)
* .model qbip1 ako:qorig NPN (BF=60 IKF=45m)
* after the replacement we have
* .model qbip1 NPN (BF=48 IS=2e-7 BF=60 IKF=45m)
* and we benefit from the fact that if parameters have
* doubled, the last entry of a parameter (e.g. BF=60)
* overwrites the previous one (BF=48).
*/
static struct card *ako_model(struct card *startcard)
{
char *newmname, *newmtype;
struct card *card, *returncard = NULL, *subcktcard = NULL;
for (card = startcard; card; card = card->nextcard) {
char *akostr, *searchname;
char *cut_line = card->line;
if (ciprefix(".subckt", cut_line))
subcktcard = card;
else if (ciprefix(".ends", cut_line))
subcktcard = NULL;
if (ciprefix(".model", cut_line)) {
if ((akostr = strstr(cut_line, "ako:")) != NULL &&
isspace_c(akostr[-1])) {
akostr += 4;
searchname = gettok(&akostr);
cut_line = nexttok(cut_line);
newmname = gettok(&cut_line);
newmtype = gettok_noparens(&akostr);
/* Find the model and do the replacement. */
if (subcktcard)
returncard = find_model(subcktcard, card, searchname,
newmname, newmtype, akostr);
if (returncard || !subcktcard)
returncard = find_model(startcard, card, searchname,
newmname, newmtype, akostr);
tfree(searchname);
tfree(newmname);
tfree(newmtype);
/* Replacement not possible, bail out. */
if (returncard)
break;
}
}
}
return returncard;
}
struct vsmodels {
char *modelname;
char *subcktline;
struct vsmodels *nextmodel;
};
/* insert a new model, just behind the given model */
static struct vsmodels *insert_new_model(
struct vsmodels *vsmodel, char *name, char *subcktline)
{
struct vsmodels *x = TMALLOC(struct vsmodels, 1);
x->nextmodel = vsmodel ? vsmodel->nextmodel : NULL;
x->modelname = copy(name);
x->subcktline = copy(subcktline);
if (vsmodel)
vsmodel->nextmodel = x;
else
vsmodel = x;
return vsmodel;
}
/* find the model */
static bool find_a_model(
struct vsmodels *vsmodel, char *name, char *subcktline)
{
struct vsmodels *x;
for (x = vsmodel; vsmodel; vsmodel = vsmodel->nextmodel)
if (eq(vsmodel->modelname, name) &&
eq(vsmodel->subcktline, subcktline))
return TRUE;
return FALSE;
}
/* delete the vsmodels list */
static bool del_models(struct vsmodels *vsmodel)
{
struct vsmodels *x;
if (!vsmodel)
return FALSE;
while (vsmodel) {
x = vsmodel->nextmodel;
tfree(vsmodel->modelname);
tfree(vsmodel->subcktline);
tfree(vsmodel);
vsmodel = x;
}
return TRUE;
}
/* Check for double '{', replace the inner '{', '}' by '(', ')'
in .subckt, .model, or .param (which all three may stem from external sources) */
static void rem_double_braces(struct card* newcard)
{
struct card* card;
int slevel = 0;
for (card = newcard; card; card = card->nextcard) {
char* cut_line = card->line;
if (ciprefix(".subckt", cut_line))
slevel++;
else if (ciprefix(".ends", cut_line))
slevel--;
if (ciprefix(".model", cut_line) || slevel > 0 || ciprefix(".param", cut_line)) {
cut_line = strchr(cut_line, '{');
if (cut_line) {
int level = 1;
cut_line++;
while (*cut_line != '\0') {
if (*cut_line == '{') {
level++;
if (level > 1)
*cut_line = '(';
}
else if (*cut_line == '}') {
if (level > 1)
*cut_line = ')';
level--;
}
cut_line++;
}
}
}
}
}
#ifdef INTEGRATE_UDEVICES
static void list_the_cards(struct card *startcard, char *prefix)
{
struct card *card;
if (!startcard) { return; }
for (card = startcard; card; card = card->nextcard) {
char* cut_line = card->line;
printf("%s %s\n", prefix, cut_line);
}
}
static struct card *the_last_card(struct card *startcard)
{
struct card *card, *lastcard = NULL;
if (!startcard) { return NULL; }
for (card = startcard; card; card = card->nextcard) {
lastcard = card;
}
return lastcard;
}
static void remove_old_cards(struct card *first, struct card *stop)
{
struct card *x, *y, *next = NULL, *nexta = NULL;
if (!first || !stop || (first == stop)) { return; }
for (x = first; (x && (x != stop)); x = next) {
if (x->line) { tfree(x->line); }
if (x->error) { tfree(x->error); }
for (y = x->actualLine; y; y = nexta) {
if (y->line) { tfree(y->line); }
if (y->error) { tfree(y->error); }
nexta = y->nextcard;
tfree(y);
}
next = x->nextcard;
tfree(x);
}
}
static struct card *u_instances(struct card *startcard)
{
struct card *card, *returncard = NULL, *subcktcard = NULL;
struct card *newcard = NULL, *last_newcard = NULL;
int models_ok = 0, models_not_ok = 0;
int udev_ok = 0, udev_not_ok = 0;
BOOL create_called = FALSE, repeat_pass = FALSE;
BOOL skip_next = FALSE;
struct card *c = startcard;
BOOL insub = FALSE;
int ps_global_tmodels = 0;
if (!cp_getvar("ps_global_tmodels", CP_NUM, &ps_global_tmodels, 0)) {
ps_global_tmodels = 0;
}
if (ps_global_tmodels) {
initialize_udevice(NULL);
/* First scan for global timing models */
while (c) {
char *line = c->line;
if (ciprefix(".subckt", line)) {
insub = TRUE;
} else if (ciprefix(".ends", line)) {
insub = FALSE;
}
if (!insub && ciprefix(".model", line)) {
(void) u_process_model_line(line, TRUE);
}
c = c->nextcard;
}
}
/* Now scan for subckts containing U* instances and local timing models */
card = startcard;
while (card) {
char *cut_line = card->line;
skip_next = FALSE;
if (ciprefix(".subckt", cut_line)) {
models_ok = models_not_ok = 0;
udev_ok = udev_not_ok = 0;
subcktcard = card;
if (!repeat_pass) {
if (create_called) {
cleanup_udevice(FALSE);
}
initialize_udevice(subcktcard->line);
create_called = TRUE;
}
} else if (ciprefix(".ends", cut_line)) {
if (repeat_pass) {
newcard = replacement_udevice_cards();
if (newcard) {
char *tmp = NULL, *pos, *posp, *new_str = NULL, *cl;
struct card* tmpc;
/* replace linenum_orig and linesource */
for (tmpc = newcard; tmpc; tmpc = tmpc->nextcard) {
tmpc->linenum_orig = subcktcard->linenum_orig;
tmpc->linesource = subcktcard->linesource;
}
DS_CREATE(ds_tmp, 128);
/* Pspice definition of .subckt card:
.SUBCKT <name> [node]*
+ [OPTIONAL: < <interface node> = <default value> >*]
+ [PARAMS: < <name> = <value> >* ]
+ [TEXT: < <name> = <text value> >* ]
...
.ENDS
*/
cl = subcktcard->line;
tmp = TMALLOC(char, strlen(cl) + 1);
(void) memcpy(tmp, cl, strlen(cl) + 1);
pos = strstr(tmp, "optional:");
posp = strstr(tmp, "params:");
ds_clear(&ds_tmp);
/* If there is an optional: and a param: then posp > pos */
if (pos) {
/* Remove the optional: section if present */
*pos = '\0';
if (posp) {
ds_cat_str(&ds_tmp, tmp);
ds_cat_str(&ds_tmp, posp);
new_str = copy(ds_get_buf(&ds_tmp));
} else {
new_str = copy(tmp);
}
} else {
new_str = copy(tmp);
}
ds_free(&ds_tmp);
tfree(tmp);
remove_old_cards(subcktcard->nextcard, card);
subcktcard->nextcard = newcard;
tfree(subcktcard->line);
subcktcard->line = new_str;
if (ft_ngdebug) {
printf("%s\n", new_str);
list_the_cards(newcard, "Replacement:");
}
last_newcard = the_last_card(newcard);
if (last_newcard) {
last_newcard->nextcard = card; // the .ends card
}
} else {
models_ok = models_not_ok = 0;
udev_ok = udev_not_ok = 0;
}
}
if (models_not_ok > 0 || udev_not_ok > 0) {
repeat_pass = FALSE;
cleanup_udevice(FALSE);
create_called = FALSE;
} else if (udev_ok > 0) {
repeat_pass = TRUE;
card = subcktcard;
skip_next = TRUE;
} else {
repeat_pass = FALSE;
cleanup_udevice(FALSE);
create_called = FALSE;
}
subcktcard = NULL;
} else if (ciprefix(".model", cut_line)) {
if (subcktcard && !repeat_pass) {
// Add .model local to subckt
if (!u_process_model_line(cut_line, FALSE)) {
models_not_ok++;
} else {
models_ok++;
}
}
} else if (ciprefix("u", cut_line) || ciprefix("x", cut_line)) {
/* U* device instance or X* instance of a subckt */
if (subcktcard) {
if (repeat_pass) {
if (!u_process_instance(cut_line)) {
repeat_pass = FALSE;
cleanup_udevice(FALSE);
create_called = FALSE;
subcktcard = NULL;
models_ok = models_not_ok = 0;
udev_ok = udev_not_ok = 0;
skip_next = FALSE;
}
} else {
if (u_check_instance(cut_line)) {
udev_ok++;
} else {
udev_not_ok++;
}
}
}
} else {
if (!ciprefix("*", cut_line)) {
udev_not_ok++;
}
}
if (!skip_next) {
card = card->nextcard;
}
}
if (create_called) {
cleanup_udevice(FALSE);
}
cleanup_udevice(TRUE);
return returncard;
}
#endif
/**** PSPICE to ngspice **************
* .model replacement in ako (a kind of) model descriptions
* replace the E source TABLE function by a B source pwl
* add predefined params TEMP, VT, GMIN to beginning of deck
* add predefined params TEMP, VT, GMIN to beginning of each .subckt call
* add .functions limit, pwr, pwrs, stp, if, int
* replace vswitch part S
S1 D S DG GND SWN
.MODEL SWN VSWITCH(VON = { 0.55 } VOFF = { 0.49 }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
* by
as1 %vd(DG GND) % gd(D S) aswn
.model aswn aswitch(cntl_off={0.49} cntl_on={0.55} r_off={1G}
+ r_on={ 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } log = TRUE)
* replace vswitch part S_ST
S1 D S DG GND S_ST
.MODEL S_ST VSWITCH(VT = { 1.5 } VH = { 0.3 }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
* by the classical voltage controlled ngspice switch
S1 D S DG GND SWN
.MODEL S_ST SW(VT = { 1.5 } VH = { 0.3 }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
switch parameter td is not yet supported
* replace & by &&
* replace | by ||
* in R instance, replace TC = xx1, xx2 by TC1=xx1 TC2=xx2
* replace T_ABS by temp and T_REL_GLOBAL by dtemp in .model cards
* get the area factor for diodes and bipolar devices
* in subcircuit .subckt and X lines with 'params:' statement
replace comma separator by space. Do nothing if comma is inside of {}.
* in .model, if double curly braces {{}}, replace the inner by {()} */
struct card *pspice_compat(struct card *oldcard)
{
struct card *card, *newcard, *nextcard;
struct vsmodels *modelsfound = NULL;
int skip_control = 0;
/* .model replacement in ako (a kind of) model descriptions
* in first .subckt and top level only */
struct card *errcard;
if ((errcard = ako_model(oldcard)) != NULL) {
fprintf(stderr, "Error: no model found for %s\n", errcard->line);
controlled_exit(1);
}
/* Process .distribution cards. */
do_distribution(oldcard);
/* replace TABLE function in E source */
replace_table(oldcard);
/* remove double braces */
rem_double_braces(oldcard);
/* add predefined params TEMP, VT, GMIN to beginning of deck */
char *new_str = copy(".param temp = 'temper'");
newcard = insert_new_line(NULL, new_str, 1, 0, "internal");
new_str = copy(".param vt = '(temper + 273.15) * 8.6173303e-5'");
nextcard = insert_new_line(newcard, new_str, 2, 0, "internal");
new_str = copy(".param gmin = 1e-12");
nextcard = insert_new_line(nextcard, new_str, 3, 0, "internal");
/* add funcs limit, pwr, pwrs, stp, if, int */
/* LIMIT( Output Expression, Limit1, Limit2)
Output will stay between the two limits given. */
new_str = copy(".func limit(x, a, b) { ternary_fcn(a > b, max(min(x, a), b), max(min(x, b), a)) }");
nextcard = insert_new_line(nextcard, new_str, 4, 0, "internal");
new_str = copy(".func pwr(x, a) { pow(x, a) }");
nextcard = insert_new_line(nextcard, new_str, 5, 0, "internal");
new_str = copy(".func pwrs(x, a) { sgn(x) * pow(x, a) }");
nextcard = insert_new_line(nextcard, new_str, 6, 0, "internal");
new_str = copy(".func stp(x) { u(x) }");
nextcard = insert_new_line(nextcard, new_str, 7, 0, "internal");
new_str = copy(".func if(a, b, c) {ternary_fcn( a , b , c )}");
nextcard = insert_new_line(nextcard, new_str, 8, 0, "internal");
new_str = copy(".func int(x) { sgn(x)*floor(abs(x)) }");
nextcard = insert_new_line(nextcard, new_str, 9, 0, "internal");
nextcard->nextcard = oldcard;
#ifdef INTEGRATE_UDEVICES
{
struct card *ucard;
#ifdef TRACE
list_the_cards(newcard, "Before udevices");
#endif
ucard = u_instances(newcard);
#ifdef TRACE
list_the_cards(newcard, "After udevices");
#endif
}
#endif
/* add predefined parameters TEMP, VT after each subckt call */
/* FIXME: This should not be necessary if we had a better sense of
hierarchy during the evaluation of TEMPER */
for (card = newcard; card; card = card->nextcard) {
char *cut_line = card->line;
if (ciprefix(".subckt", cut_line)) {
new_str = copy(".param temp = 'temper'");
nextcard = insert_new_line(card, new_str, 0, card->linenum_orig, card->linesource);
new_str = copy(".param vt = '(temper + 273.15) * 8.6173303e-5'");
nextcard = insert_new_line(nextcard, new_str, 1, card->linenum_orig, card->linesource);
/* params: replace comma separator by space.
Do nothing if you are inside of { }. */
char* parastr = strstr(cut_line, "params:");
int brace = 0;
if (parastr) {
parastr += 8;
while (*parastr) {
if (*parastr == '{')
brace++;
else if (*parastr == '}')
brace--;
if (brace == 0 && *parastr == ',')
*parastr = ' ';
parastr++;
}
}
}
}
/* .model xxx NMOS/PMOS level=6 --> level = 8, version=3.2.4
.model xxx NMOS/PMOS level=7 --> level = 8, version=3.2.4
.model xxx NMOS/PMOS level=5 --> level = 44
.model xxx NMOS/PMOS level=8 --> level = 14, version=4.5.0
.model xxx NPN/PNP level=2 --> level = 6
.model xxx LPNP level=n --> level = 1 subs=-1
Remove any Monte - Carlo variation parameters from .model cards.*/
for (card = newcard; card; card = card->nextcard) {
char* cut_line = card->line;
if (ciprefix(".model", cut_line)) {
char* modname, *modtype, *curr_line;
int i;
char *cut_del = curr_line = cut_line = inp_remove_ws(copy(cut_line));
cut_line = nexttok(cut_line); /* skip .model */
modname = gettok(&cut_line); /* save model name */
if (!modname) {
fprintf(stderr, "Error: No model name given for %s\n", curr_line);
controlled_exit(EXIT_BAD);
}
modtype = gettok_noparens(&cut_line); /* save model type */
if (!modtype) {
fprintf(stderr, "Error: No model type given for %s\n", curr_line);
controlled_exit(EXIT_BAD);
}
if (cieq(modtype, "NMOS") || cieq(modtype, "PMOS")) {
char* lv = strstr(cut_line, "level=");
if (lv) {
int ll;
lv = lv + 6;
char* ntok = gettok(&lv);
ll = atoi(ntok);
switch (ll) {
case 5:
{
/* EKV 2.6 in the adms branch */
char* newline = tprintf(".model %s %s level=44 %s", modname, modtype, lv);
tfree(card->line);
card->line = curr_line = newline;
}
break;
case 6:
case 7:
{
/* BSIM3 version 3.2.4 */
char* newline = tprintf(".model %s %s level=8 version=3.2.4 %s", modname, modtype, lv);
tfree(card->line);
card->line = curr_line = newline;
}
break;
case 8:
{
/* BSIM4 version 4.5.0 */
char* newline = tprintf(".model %s %s level=14 version=4.5.0 %s", modname, modtype, lv);
tfree(card->line);
card->line = curr_line = newline;
}
break;
default:
break;
}
tfree(ntok);
}
}
else if (cieq(modtype, "NPN") || cieq(modtype, "PNP")) {
char* lv = strstr(cut_line, "level=");
if (lv) {
int ll;
lv = lv + 6;
char* ntok = gettok(&lv);
ll = atoi(ntok);
switch (ll) {
case 2:
{
/* MEXTRAM 504.12.1 in the adms branch */
char* newline = tprintf(".model %s %s level=6 %s", modname, modtype, lv);
tfree(card->line);
card->line = curr_line = newline;
}
break;
default:
break;
}
tfree(ntok);
}
}
else if (cieq(modtype, "LPNP")) {
/* lateral PNP enabled */
char* newline = tprintf(".model %s PNP level=1 subs=-1 %s", modname, cut_line);
tfree(card->line);
card->line = curr_line = newline;
}
tfree(modname);
tfree(modtype);
/* Remove any Monte-Carlo variation parameters. They qualify
* a previous parameter, so there must be at least 3 tokens.
* There are two keywords "dev" (different values for each device),
* and "lot" (all devices of this model share a value).
* The keyword may be optionally followed by '/' and
* a probability distribution name, then there must be '=' and
* a value, then an optional '%' indicating relative rather than
* absolute variation. Allow muliple lot and dev on a single .model line.
*/
bool remdevlot = FALSE;
cut_line = curr_line;
for (i = 0; i < 3; i++)
cut_line = nexttok(cut_line);
while (cut_line) {
if (!strncmp(cut_line, "dev=", 4) ||
!strncmp(cut_line, "lot=", 4)) {
while (*cut_line && !isspace_c(*cut_line)) {
*cut_line++ = ' ';
}
remdevlot = TRUE;
cut_line = skip_ws(cut_line);
continue;
}
cut_line = nexttok(cut_line);
}
if (remdevlot) {
tfree(card->line);
card->line = curr_line;
}
else
tfree(cut_del);
} // if .model
} // for loop through all cards
/* x ... params: p1=val1, p2=val2 replace comma separator by space.
Do nothing if you are inside of { }. */
for (card = newcard; card; card = card->nextcard) {
char* cut_line = card->line;
if (ciprefix("x", cut_line)) {
char* parastr = strstr(cut_line, "params:");
int brace = 0;
if (parastr) {
parastr += 8;
while (*parastr) {
if (*parastr == '{')
brace++;
else if (*parastr == '}')
brace--;
if (brace == 0 && *parastr == ',')
*parastr = ' ';
parastr++;
}
}
}
}
/* in R instance, replace TC = xx1, xx2 by TC1=xx1 TC2=xx2 */
for (card = newcard; card; card = card->nextcard) {
char *cut_line = card->line;
/* exclude any command inside .control ... .endc */
if (ciprefix(".control", cut_line)) {
skip_control++;
continue;
}
else if (ciprefix(".endc", cut_line)) {
skip_control--;
continue;
}
else if (skip_control > 0) {
continue;
}
if (*cut_line == 'r' || *cut_line == 'l' || *cut_line == 'c') {
/* Skip name and two nodes */
char *ntok = nexttok(cut_line);
ntok = nexttok(ntok);
ntok = nexttok(ntok);
if (!ntok || *ntok == '\0') {
fprintf(stderr, "Error: Missing token in line %d:\n%s\n",
card->linenum, cut_line);
fprintf(stderr, " Please correct the input file\n");
controlled_exit(1);
}
char *tctok = search_plain_identifier(ntok, "tc");
if (tctok) {
char *tc1, *tc2;
char *tctok1 = strchr(tctok, '=');
if (tctok1)
/* skip '=' */
tctok1 += 1;
else
/* no '=' found, skip 'tc' */
tctok1 = tctok + 2;
/* tc1 may be an expression, enclosed in {} */
if (*tctok1 == '{') {
tc1 = gettok_char(&tctok1, '}', TRUE, TRUE);
}
else {
tc1 = gettok_node(&tctok1);
}
/* skip spaces and commas */
while (isspace_c(*tctok1) || (*tctok1 == ','))
tctok1++;
/* tc2 may be an expression, enclosed in {} */
if (*tctok1 == '{') {
tc2 = gettok_char(&tctok1, '}', TRUE, TRUE);
}
else {
tc2 = gettok_node(&tctok1);
}
tctok[-1] = '\0';
char *newstring;
if (tc1 && tc2)
newstring = tprintf("%s tc1=%s tc2=%s",
cut_line, tc1, tc2);
else if (tc1)
newstring = tprintf("%s tc1=%s", cut_line, tc1);
else {
fprintf(stderr,
"Warning: tc without parameters removed in line "
"\n %s\n",
cut_line);
continue;
}
tfree(card->line);
card->line = newstring;
tfree(tc1);
tfree(tc2);
}
}
}
/* replace & with && , | with || , *# with * # , and ~ with ! */
for (card = newcard; card; card = card->nextcard) {
char *t;
char *cut_line = card->line;
/* we don't have command lines in a PSPICE model */
if (ciprefix("*#", cut_line)) {
char *tmpstr = tprintf("* #%s", cut_line + 2);
tfree(card->line);
card->line = tmpstr;
continue;
}
if (*cut_line == '*')
continue;
if (*cut_line == '\0')
continue;
/* exclude any command inside .control ... .endc */
if (ciprefix(".control", cut_line)) {
skip_control++;
continue;
}
else if (ciprefix(".endc", cut_line)) {
skip_control--;
continue;
}
else if (skip_control > 0) {
continue;
}
if ((t = strstr(card->line, "&")) != NULL) {
while (t && (t[1] != '&')) {
char *tt = NULL;
char *tn = copy(t + 1); /*skip |*/
char *strbeg = copy_substring(card->line, t);
tfree(card->line);
card->line = tprintf("%s&&%s", strbeg, tn);
tfree(strbeg);
tfree(tn);
t = card->line;
while ((t = strstr(t, "&&")) != NULL)
tt = t = t + 2;
if (!tt)
break;
else
t = strstr(tt, "&");
}
}
if ((t = strstr(card->line, "|")) != NULL) {
while (t && (t[1] != '|')) {
char *tt = NULL;
char *tn = copy(t + 1); /*skip |*/
char *strbeg = copy_substring(card->line, t);
tfree(card->line);
card->line = tprintf("%s||%s", strbeg, tn);
tfree(strbeg);
tfree(tn);
t = card->line;
while ((t = strstr(t, "||")) != NULL)
tt = t = t + 2;
if (!tt)
break;
else
t = strstr(tt, "|");
}
}
/* We may have '~' in path names or A devices */
if (ciprefix(".inc", card->line) || ciprefix(".lib", card->line) ||
ciprefix("A", card->line))
continue;
if ((t = strstr(card->line, "~")) != NULL) {
while (t) {
*t = '!';
t = strstr(t, "~");
}
}
}
/* replace T_ABS by temp, T_REL_GLOBAL by dtemp, and T_MEASURED by TNOM
in .model cards. What about T_REL_LOCAL ? T_REL_LOCAL is used in
conjunction with AKO and is not yet implemented. */
for (card = newcard; card; card = card->nextcard) {
char *cut_line = card->line;
if (ciprefix(".model", cut_line)) {
char *t_str;
if ((t_str = strstr(cut_line, "t_abs")) != NULL)
memcpy(t_str, " temp", 5);
else if ((t_str = strstr(cut_line, "t_rel_global")) != NULL)
memcpy(t_str, " dtemp", 12);
else if ((t_str = strstr(cut_line, "t_measured")) != NULL)
memcpy(t_str, " tnom", 10);
}
}
/* get the area factor for diodes and bipolar devices
d1 n1 n2 dmod 7 --> d1 n1 n2 dmod area=7
q2 n1 n2 n3 [n4] bjtmod 1.35 --> q2 n1 n2 n3 n4 bjtmod area=1.35
q3 1 2 3 4 bjtmod 1.45 --> q2 1 2 3 4 bjtmod area=1.45
*/
for (card = newcard; card; card = card->nextcard) {
char *cut_line = card->line;
if (*cut_line == '*')
continue;
// exclude any command inside .control ... .endc
if (ciprefix(".control", cut_line)) {
skip_control++;
continue;
}
else if (ciprefix(".endc", cut_line)) {
skip_control--;
continue;
}
else if (skip_control > 0) {
continue;
}
if (*cut_line == 'q') {
/* According to PSPICE Reference Guide the fourth (substrate) node
has to be put into [] if it is not just a number */
cut_line = nexttok(cut_line); //.model
cut_line = nexttok(cut_line); // node1
cut_line = nexttok(cut_line); // node2
cut_line = nexttok(cut_line); // node3
if (!cut_line || *cut_line == '\0') {
fprintf(stderr, "Line no. %d, %s, missing tokens\n",
card->linenum_orig, card->line);
if (ft_stricterror)
controlled_exit(1);
else
continue;
}
if (*cut_line == '[') { // node4 not a number
*cut_line = ' ';
cut_line = strchr(cut_line, ']');
*cut_line = ' ';
cut_line = skip_ws(cut_line);
cut_line = nexttok(cut_line); // model name
}
else { // if an integer number, it is node4
bool is_node4 = TRUE;
while (*cut_line && !isspace_c(*cut_line))
if (!isdigit_c(*cut_line++))
is_node4 = FALSE; // already model name
if (is_node4) {
cut_line = nexttok(cut_line); // model name
}
}
if (cut_line && *cut_line &&
atof(cut_line) > 0.0) { // size of area is a real number
char *tmpstr1 = copy_substring(card->line, cut_line);
char *tmpstr2 = tprintf("%s area=%s", tmpstr1, cut_line);
tfree(tmpstr1);
tfree(card->line);
card->line = tmpstr2;
}
else if (cut_line && *cut_line &&
*(skip_ws(cut_line)) ==
'{') { // size of area is parametrized inside {}
char *tmpstr1 = copy_substring(card->line, cut_line);
char *tmpstr2 = gettok_char(&cut_line, '}', TRUE, TRUE);
char *tmpstr3 =
tprintf("%s area=%s %s", tmpstr1, tmpstr2, cut_line);
tfree(tmpstr1);
tfree(tmpstr2);
tfree(card->line);
card->line = tmpstr3;
}
}
else if (*cut_line == 'd') {
cut_line = nexttok(cut_line); //.model
cut_line = nexttok(cut_line); // node1
cut_line = nexttok(cut_line); // node2
if (!cut_line || *cut_line == '\0') {
fprintf(stderr, "Line no. %d, %s, missing tokens\n",
card->linenum_orig, card->line);
if (ft_stricterror)
controlled_exit(1);
else
continue;
}
cut_line = nexttok(cut_line); // model name
if (cut_line && *cut_line &&
atof(cut_line) > 0.0) { // size of area
char *tmpstr1 = copy_substring(card->line, cut_line);
char *tmpstr2 = tprintf("%s area=%s", tmpstr1, cut_line);
tfree(tmpstr1);
tfree(card->line);
card->line = tmpstr2;
}
}
}
/* if vswitch part s, replace
* S1 D S DG GND SWN
* .MODEL SWN VSWITCH ( VON = {0.55} VOFF = {0.49}
* RON={1/(2*M*(W/LE)*(KPN/2)*10)} ROFF={1G} )
* by
* a1 %v(DG) %gd(D S) swa
* .MODEL SWA aswitch(cntl_off=0.49 cntl_on=0.55 r_off=1G
* r_on={1/(2*M*(W/LE)*(KPN/2)*10)} log=TRUE)
*
* if vswitch part s_st, don't replace instance, only model
* replace
* S1 D S DG GND S_ST
* .MODEL S_ST VSWITCH(VT = { 1.5 } VH = { 0.s }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
* by the classical voltage controlled ngspice switch
* S1 D S DG GND S_ST
* .MODEL S_ST SW(VT = { 1.5 } VH = { 0.s }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
* vswitch delay parameter td is not yet supported
* simple hierachy, as nested subcircuits are not allowed in PSPICE */
/* first scan: find the vswitch models, transform them and put the S models
into a list */
for (card = newcard; card; card = card->nextcard) {
char *str;
static struct card *subcktline = NULL;
static int nesting = 0;
char *cut_line = card->line;
if (ciprefix(".subckt", cut_line)) {
subcktline = card;
nesting++;
}
if (ciprefix(".ends", cut_line))
nesting--;
if (ciprefix(".model", card->line) && strstr(card->line, "vswitch")) {
char *modname;
str = card->line = inp_remove_ws(card->line);
str = nexttok(str); /* throw away '.model' */
INPgetNetTok(&str, &modname, 0); /* model name */
if (!ciprefix("vswitch", str)) {
tfree(modname);
continue;
}
str = nexttok_noparens(str); /* throw away 'vswitch' */
/* S_ST switch (parameters ron, roff, vt, vh)
* we have to find 0 to 4 parameters, identified by 'vh=' etc.
* Parameters not found have to be replaced by their default values. */
if (strstr(str, "vt=") || strstr(str, "vh=")) {
char* newstr;
char* lstr = copy(str);
char* partstr = strstr(lstr, "ron=");
if (!partstr) {
newstr = tprintf("%s %s", "ron=1.0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
partstr = strstr(lstr, "roff=");
if (!partstr) {
newstr = tprintf("%s %s", "roff=1.0e12", lstr); //default value
tfree(lstr);
lstr = newstr;
}
partstr = strstr(lstr, "vt=");
if (!partstr) {
newstr = tprintf("%s %s", "vt=0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
partstr = strstr(lstr, "vh=");
if (!partstr) {
newstr = tprintf("%s %s", "vh=0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
tfree(card->line);
if (lstr[strlen(lstr) - 1] == ')')
card->line = tprintf(".model %s sw ( %s", modname, lstr);
else
card->line = tprintf(".model %s sw %s", modname, lstr);
tfree(lstr);
tfree(modname);
}
/* S vswitch (parameters ron, roff, von, voff) */
/* We have to find 0 to 4 parameters, identified by 'von=' etc. and
* replace them by the pswitch code model parameters
* replace VON by cntl_on, VOFF by cntl_off, RON by r_on, and ROFF by r_off.
* Parameters not found have to be replaced by their default values. */
else if (strstr(str, "von=") || strstr(str, "voff=")) {
char* newstr, *begstr;
char* lstr = copy(str);
/* ron */
char* partstr = strstr(lstr, "ron=");
if (!partstr) {
newstr = tprintf("%s %s", "r_on=1.0", lstr); //default value
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s r_on%s", begstr, partstr + 3);
tfree(begstr);
}
tfree(lstr);
lstr = newstr;
/* roff */
partstr = strstr(lstr, "roff=");
if (!partstr) {
newstr = tprintf("%s %s", "r_off=1.0e6", lstr); //default value
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s r_off%s", begstr, partstr + 4);
tfree(begstr);
}
tfree(lstr);
lstr = newstr;
/* von */
partstr = strstr(lstr, "von=");
if (!partstr) {
newstr = tprintf("%s %s", "cntl_on=1", lstr); //default value
tfree(lstr);
lstr = newstr;
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s cntl_on%s", begstr, partstr + 3);
tfree(begstr);
}
tfree(lstr);
lstr = newstr;
/* voff */
partstr = strstr(lstr, "voff=");
if (!partstr) {
newstr = tprintf("%s %s", "cntl_off=0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s cntl_off%s", begstr, partstr + 4);
tfree(begstr);
}
tfree(lstr);
lstr = newstr;
tfree(card->line);
if (lstr[strlen(lstr) - 1] == ')')
card->line = tprintf(".model a%s pswitch( log=TRUE %s", modname, lstr);
else
card->line = tprintf(".model a%s pswitch(%s log=TRUE)", modname, lstr);
tfree(lstr);
/* add to list, to change vswitch instance to code model line */
if (nesting > 0)
modelsfound = insert_new_model(
modelsfound, modname, subcktline->line);
else
modelsfound = insert_new_model(modelsfound, modname, "top");
tfree(modname);
}
else {
fprintf(stderr, "Error: Bad switch model in line %s\n", card->line);
}
}
}
/* no need to continue if no vswitch is found */
if (!modelsfound)
goto iswi;
/* second scan: find the switch instances s calling a vswitch model and
* transform them */
for (card = newcard; card; card = card->nextcard) {
static struct card *subcktline = NULL;
static int nesting = 0;
char *cut_line = card->line;
if (*cut_line == '*')
continue;
// exclude any command inside .control ... .endc
if (ciprefix(".control", cut_line)) {
skip_control++;
continue;
}
else if (ciprefix(".endc", cut_line)) {
skip_control--;
continue;
}
else if (skip_control > 0) {
continue;
}
if (ciprefix(".subckt", cut_line)) {
subcktline = card;
nesting++;
}
if (ciprefix(".ends", cut_line))
nesting--;
if (ciprefix("s", cut_line)) {
/* check for the model name */
int i;
bool good = TRUE;
char *stoks[6];
for (i = 0; i < 6; i++) {
stoks[i] = gettok_node(&cut_line);
if (!stoks[i]) {
fprintf(stderr,
"Error: bad syntax in line %d\n %s\n"
"from file\n"
" %s\n",
card->linenum_orig, card->line, card->linesource);
good = FALSE;
break;
}
}
if (!good) {
for (i = 0; i < 6; i++)
tfree(stoks[i]);
continue;
}
/* rewrite s line and replace it if a model is found */
if ((nesting > 0) &&
find_a_model(modelsfound, stoks[5], subcktline->line)) {
tfree(card->line);
card->line = tprintf("a%s %%gd(%s %s) %%gd(%s %s) a%s",
stoks[0], stoks[3], stoks[4], stoks[1], stoks[2],
stoks[5]);
}
/* if model is not within same subcircuit, search at top level */
else if (find_a_model(modelsfound, stoks[5], "top")) {
tfree(card->line);
card->line = tprintf("a%s %%gd(%s %s) %%gd(%s %s) a%s",
stoks[0], stoks[3], stoks[4], stoks[1], stoks[2],
stoks[5]);
}
for (i = 0; i < 6; i++)
tfree(stoks[i]);
}
}
del_models(modelsfound);
modelsfound = NULL;
iswi:;
/* if iswitch part s, replace
* W1 D S VC SWN
* .MODEL SWN ISWITCH ( ION = {0.55} IOFF = {0.49}
* RON={1/(2*M*(W/LE)*(KPN/2)*10)} ROFF={1G} )
* by
* a1 %v(DG) %gd(D S) swa
* .MODEL SWA aswitch(cntl_off=0.49 cntl_on=0.55 r_off=1G
* r_on={1/(2*M*(W/LE)*(KPN/2)*10)} log=TRUE)
*
* if iswitch part s_st (short transition), don't replace instance, but only model
* replace
* W1 D S VC S_ST
* .MODEL S_ST ISWITCH(IT = { 1.5 } IH = { 0.2 }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
* by the classical current controlled ngspice switch
* W1 D S DG GND S_ST
* .MODEL S_ST CSW(IT = { 1.5 } IH = { 0.2 }
RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } ROFF = { 1G })
* iswitch delay parameter td is not yet supported
* simple hierachy, as nested subcircuits are not allowed in PSPICE */
/* first scan: find the iswitch models, transform them and put them into a
* list */
for (card = newcard; card; card = card->nextcard) {
char* str;
static struct card* subcktline = NULL;
static int nesting = 0;
char* cut_line = card->line;
if (ciprefix(".subckt", cut_line)) {
subcktline = card;
nesting++;
}
if (ciprefix(".ends", cut_line))
nesting--;
if (ciprefix(".model", card->line) && strstr(card->line, "iswitch")) {
char* modname;
card->line = str = inp_remove_ws(card->line);
str = nexttok(str); /* throw away '.model' */
INPgetNetTok(&str, &modname, 0); /* model name */
if (!ciprefix("iswitch", str)) {
tfree(modname);
continue;
}
str = nexttok_noparens(str); /* throw away 'iswitch' */
/* S_ST switch (parameters ron, roff, it, ih)
* we have to find 0 to 4 parameters, identified by 'ih=' etc.
* Parameters not found have to be replaced by their default values. */
if (strstr(str, "it=") || strstr(str, "ih=")) {
char* newstr;
char* lstr = copy(str);
char* partstr = strstr(lstr, "ron=");
if (!partstr) {
newstr = tprintf("%s %s", "ron=1.0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
partstr = strstr(lstr, "roff=");
if (!partstr) {
newstr = tprintf("%s %s", "roff=1.0e12", lstr); //default value
tfree(lstr);
lstr = newstr;
}
partstr = strstr(lstr, "it=");
if (!partstr) {
newstr = tprintf("%s %s", "it=0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
partstr = strstr(lstr, "ih=");
if (!partstr) {
newstr = tprintf("%s %s", "ih=0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
tfree(card->line);
if (lstr[strlen(lstr) - 1] == ')')
card->line = tprintf(".model %s csw ( %s", modname, lstr);
else
card->line = tprintf(".model %s csw %s", modname, lstr);
tfree(lstr);
tfree(modname);
}
/* S vswitch (parameters ron, roff, ion, ioff) */
/* We have to find 0 to 4 parameters, identified by 'ion=' etc. and
* replace them by the pswitch code model parameters
* replace VON by cntl_on, VOFF by cntl_off, RON by r_on, and ROFF by r_off.
* Parameters not found have to be replaced by their default values. */
else if (strstr(str, "ion=") || strstr(str, "ioff=")) {
char* newstr, * begstr;
char* lstr = copy(str);
/* ron */
char* partstr = strstr(lstr, "ron=");
if (!partstr) {
newstr = tprintf("%s %s", "r_on=1.0", lstr); //default value
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s r_on%s", begstr, partstr + 3);
}
tfree(lstr);
lstr = newstr;
/* roff */
partstr = strstr(lstr, "roff=");
if (!partstr) {
newstr = tprintf("%s %s", "r_off=1.0e6", lstr); //default value
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s r_off%s", begstr, partstr + 4);
}
tfree(lstr);
lstr = newstr;
/* von */
partstr = strstr(lstr, "ion=");
if (!partstr) {
newstr = tprintf("%s %s", "cntl_on=1", lstr); //default value
tfree(lstr);
lstr = newstr;
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s cntl_on%s", begstr, partstr + 3);
}
tfree(lstr);
lstr = newstr;
/* voff */
partstr = strstr(lstr, "ioff=");
if (!partstr) {
newstr = tprintf("%s %s", "cntl_off=0", lstr); //default value
tfree(lstr);
lstr = newstr;
}
else {
begstr = copy_substring(lstr, partstr);
newstr = tprintf("%s cntl_off%s", begstr, partstr + 4);
}
tfree(lstr);
lstr = newstr;
tfree(card->line);
if (lstr[strlen(lstr) - 1] == ')')
card->line = tprintf(".model a%s aswitch( log=TRUE limit=TRUE %s", modname, lstr);
else
card->line = tprintf(".model a%s aswitch(%s log=TRUE limit=TRUE)", modname, lstr);
tfree(lstr);
/* add to list, to change vswitch instance to code model line */
if (nesting > 0)
modelsfound = insert_new_model(
modelsfound, modname, subcktline->line);
else
modelsfound = insert_new_model(modelsfound, modname, "top");
tfree(modname);
}
else {
fprintf(stderr, "Error: Bad switch model in line %s\n", card->line);
}
}
}
#if(0)
/* we have to find 4 parameters, identified by '=', separated by
* spaces */
char* equalptr[4];
equalptr[0] = strstr(str, "=");
if (!equalptr[0]) {
fprintf(stderr,
"Error: not enough parameters in iswitch model\n "
"%s\n",
card->line);
controlled_exit(1);
}
for (i = 1; i < 4; i++) {
equalptr[i] = strstr(equalptr[i - 1] + 1, "=");
if (!equalptr[i]) {
fprintf(stderr,
"Error: not enough parameters in iswitch model\n "
" %s\n",
card->line);
controlled_exit(1);
}
}
for (i = 0; i < 4; i++) {
equalptr[i] = skip_back_ws(equalptr[i], str);
while (*(equalptr[i]) != '(' && !isspace_c(*(equalptr[i])) &&
*(equalptr[i]) != ',')
(equalptr[i])--;
(equalptr[i])++;
}
for (i = 0; i < 3; i++)
modpar[i] = copy_substring(equalptr[i], equalptr[i + 1] - 1);
if (strrchr(equalptr[3], ')'))
modpar[3] = copy_substring(
equalptr[3], strrchr(equalptr[3], ')'));
else
/* iswitch defined without parens */
modpar[3] = copy(equalptr[3]);
/* check if we have parameters IT and IH */
for (i = 0; i < 4; i++) {
if (ciprefix("ih", modpar[i]))
have_ih = TRUE;
if (ciprefix("it", modpar[i]))
have_it = TRUE;
}
if (have_ih && have_it) {
/* replace iswitch by csw */
char* vs = strstr(card->line, "iswitch");
memmove(vs, " csw", 7);
}
else {
/* replace ION by cntl_on, IOFF by cntl_off, RON by r_on, and
* ROFF by r_off */
tfree(card->line);
rep_spar(modpar);
card->line = tprintf(
/* FIXME: a new switch derived from pswitch with vnam input is due */
".model a%s aswitch(%s %s %s %s log=TRUE limit=TRUE)", modname,
modpar[0], modpar[1], modpar[2], modpar[3]);
}
for (i = 0; i < 4; i++)
tfree(modpar[i]);
if (nesting > 0)
modelsfound = insert_new_model(
modelsfound, modname, subcktline->line);
else
modelsfound = insert_new_model(modelsfound, modname, "top");
tfree(modname);
}
}
#endif
/* no need to continue if no iswitch is found */
if (!modelsfound)
return newcard;
/* second scan: find the switch instances s calling an iswitch model and
* transform them */
for (card = newcard; card; card = card->nextcard) {
static struct card* subcktline = NULL;
static int nesting = 0;
char* cut_line = card->line;
if (*cut_line == '*')
continue;
// exclude any command inside .control ... .endc
if (ciprefix(".control", cut_line)) {
skip_control++;
continue;
}
else if (ciprefix(".endc", cut_line)) {
skip_control--;
continue;
}
else if (skip_control > 0) {
continue;
}
if (ciprefix(".subckt", cut_line)) {
subcktline = card;
nesting++;
}
if (ciprefix(".ends", cut_line))
nesting--;
if (ciprefix("w", cut_line)) {
/* check for the model name */
int i;
char* stoks[5];
for (i = 0; i < 5; i++)
stoks[i] = gettok_node(&cut_line);
/* rewrite w line and replace it if a model is found */
if ((nesting > 0) &&
find_a_model(modelsfound, stoks[4], subcktline->line)) {
tfree(card->line);
card->line = tprintf("a%s %%vnam(%s) %%gd(%s %s) a%s",
stoks[0], stoks[3], stoks[1], stoks[2],
stoks[4]);
}
/* if model is not within same subcircuit, search at top level */
else if (find_a_model(modelsfound, stoks[4], "top")) {
tfree(card->line);
card->line = tprintf("a%s %%vnam(%s) %%gd(%s %s) a%s",
stoks[0], stoks[3], stoks[1], stoks[2],
stoks[4]);
}
for (i = 0; i < 5; i++)
tfree(stoks[i]);
}
}
del_models(modelsfound);
return newcard;
}
/* do not modify oldcard address, insert everything after first line only */
void pspice_compat_a(struct card *oldcard)
{
oldcard->nextcard = pspice_compat(oldcard->nextcard);
}
/**** LTSPICE to ngspice **************
* add functions uplim, dnlim
* Replace
* D1 A K SDMOD
* .MODEL SDMOD D (Roff=1000 Ron=0.7 Rrev=0.2 Vfwd=1 Vrev=10 Revepsilon=0.2
* Epsilon=0.2 Ilimit=7 Revilimit=7)
* by
* ad1 a k asdmod
* .model asdmod sidiode(Roff=1000 Ron=0.7 Rrev=0.2 Vfwd=1 Vrev=10
* Revepsilon=0.2 Epsilon=0.2 Ilimit=7 Revilimit=7)
* Remove '.backanno'
*/
struct card *ltspice_compat(struct card *oldcard)
{
struct card *card, *newcard, *nextcard;
struct vsmodels *modelsfound = NULL;
int skip_control = 0;
/* remove double braces only if not yet done in pspice_compat() */
if (!newcompat.ps)
rem_double_braces(oldcard);
/* add funcs uplim, dnlim to beginning of deck */
char *new_str =
copy(".func uplim(x, pos, z) { min(x, pos - z) + (1 - "
"(min(max(0, x - pos + z), 2 * z) / 2 / z - 1)**2)*z }");
newcard = insert_new_line(NULL, new_str, 1, 0, "internal");
new_str = copy(".func dnlim(x, neg, z) { max(x, neg + z) - (1 - "
"(min(max(0, -x + neg + z), 2 * z) / 2 / z - 1)**2)*z }");
nextcard = insert_new_line(newcard, new_str, 2, 0, "internal");
new_str = copy(".func uplim_tanh(x, pos, z) { min(x, pos - z) + "
"tanh(max(0, x - pos + z) / z)*z }");
nextcard = insert_new_line(nextcard, new_str, 3, 0, "internal");
new_str = copy(".func dnlim_tanh(x, neg, z) { max(x, neg + z) - "
"tanh(max(0, neg + z - x) / z)*z }");
nextcard = insert_new_line(nextcard, new_str, 4, 0, "internal");
nextcard->nextcard = oldcard;
/* remove .backanno, replace 'noiseless' by 'moisy=0' */
for (card = nextcard; card; card = card->nextcard) {
char* cut_line = card->line;
if (ciprefix(".backanno", cut_line)) {
*cut_line = '*';
}
else if (*cut_line == 'r') {
char* noi = strstr(cut_line, "noiseless");
/* only if 'noiseless' is an unconnected token */
if (noi && isspace_c(noi[-1]) && (isspace_c(noi[9]) || !isprint_c(noi[9]))) {
memcpy(noi, "noisy=0 ", 9);
}
}
}
/* replace
* D1 A K SDMOD
* .MODEL SDMOD D (Roff=1000 Ron=0.7 Rrev=0.2 Vfwd=1 Vrev=10
* Revepsilon=0.2 Epsilon=0.2 Ilimit=7 Revilimit=7)
* by
* a1 a k SDMOD
* .model SDMOD sidiode(Roff=1000 Ron=0.7 Rrev=0.2 Vfwd=1 Vrev=10
* Revepsilon=0.2 Epsilon=0.2 Ilimit=7 Revilimit=7)
* Do this if one of the parameters, which are uncommon to standard diode
* model, has been found.
* simple hierachy, as nested subcircuits are not allowed in PSPICE */
/* first scan: find the d models, transform them and put them into a list
*/
for (card = nextcard; card; card = card->nextcard) {
char *str;
static struct card *subcktline = NULL;
static int nesting = 0;
char *cut_line = card->line;
if (*cut_line == '*' || *cut_line == '\0')
continue;
else if (ciprefix(".subckt", cut_line)) {
subcktline = card;
nesting++;
}
else if (ciprefix(".ends", cut_line))
nesting--;
else if (ciprefix(".model", card->line) &&
search_plain_identifier(card->line, "d")) {
if (search_plain_identifier(card->line, "roff") ||
search_plain_identifier(card->line, "ron") ||
search_plain_identifier(card->line, "rrev") ||
search_plain_identifier(card->line, "vfwd") ||
search_plain_identifier(card->line, "vrev") ||
search_plain_identifier(card->line, "revepsilon") ||
search_plain_identifier(card->line, "epsilon") ||
search_plain_identifier(card->line, "revilimit") ||
search_plain_identifier(card->line, "ilimit")) {
char *modname;
/* remove parameter 'noiseless' (the model is noiseless anyway) */
char *nonoise = search_plain_identifier(card->line, "noiseless");
if (nonoise) {
size_t iii;
for (iii = 0; iii < 9; iii++)
nonoise[iii] = ' ';
}
card->line = str = inp_remove_ws(card->line);
str = nexttok(str); /* throw away '.model' */
INPgetNetTok(&str, &modname, 0); /* model name */
if (!ciprefix("d", str)) {
tfree(modname);
continue;
}
/* skip d */
str++;
/* we take all the existing parameters */
char *newstr = copy(str);
tfree(card->line);
card->line = tprintf(".model a%s sidiode%s", modname, newstr);
if (nesting > 0)
modelsfound = insert_new_model(
modelsfound, modname, subcktline->line);
else
modelsfound =
insert_new_model(modelsfound, modname, "top");
tfree(modname);
tfree(newstr);
}
}
else
continue;
}
/* no need to continue if no d is found */
if (!modelsfound)
return newcard;
/* second scan: find the diode instances d calling a simple diode model
* and transform them */
for (card = nextcard; card; card = card->nextcard) {
static struct card *subcktline = NULL;
static int nesting = 0;
char *cut_line = card->line;
if (*cut_line == '*')
continue;
if (*cut_line == '\0')
continue;
// exclude any command inside .control ... .endc
if (ciprefix(".control", cut_line)) {
skip_control++;
continue;
}
else if (ciprefix(".endc", cut_line)) {
skip_control--;
continue;
}
else if (skip_control > 0) {
continue;
}
if (ciprefix(".subckt", cut_line)) {
subcktline = card;
nesting++;
}
if (ciprefix(".ends", cut_line))
nesting--;
if (ciprefix("d", cut_line)) {
/* check for the model name */
int i;
char *stoks[4];
for (i = 0; i < 4; i++) {
stoks[i] = gettok_node(&cut_line);
if (stoks[i] == NULL) {
fprintf(stderr, "Error in line %d: buggy diode instance line\n %s\n", card->linenum_orig, card->linesource);
fprintf(stderr, "At least 'Dxx n1 n2 d' is required.\n");
controlled_exit(EXIT_BAD);
}
}
/* rewrite d line and replace it if a model is found */
if ((nesting > 0) &&
find_a_model(modelsfound, stoks[3], subcktline->line)) {
tfree(card->line);
card->line = tprintf("a%s %s %s a%s",
stoks[0], stoks[1], stoks[2], stoks[3]);
}
/* if model is not within same subcircuit, search at top level */
else if (find_a_model(modelsfound, stoks[3], "top")) {
tfree(card->line);
card->line = tprintf("a%s %s %s a%s",
stoks[0], stoks[1], stoks[2], stoks[3]);
}
for (i = 0; i < 4; i++)
tfree(stoks[i]);
}
}
del_models(modelsfound);
return newcard;
}
/* do not modify oldcard address, insert everything after first line only */
void ltspice_compat_a(struct card *oldcard)
{
oldcard->nextcard = ltspice_compat(oldcard->nextcard);
}