/* d_logic.c  94.12.09
 * Copyright 1983-1992   Albert Davis
 * logic model.
 */
#include "ecah.h"
#include "argparse.h"
#include "branch.h"
#include "error.h"
#include "mode.h"
#include "nodestat.h"
#include "options.h"
#include "status.h"
#include "types.h"
#include "d_logic.h"
#include "d_subckt.h"
#include "declare.h"
/*--------------------------------------------------------------------------*/
static 	branch_t *create_logic(const functions_t*);
static 	branch_t *copy_logic(const branch_t*);
static 	void    parse_logic(branch_t*,const char*,int*);
static 	void    print_logic(const branch_t*,int,int);
static	branch_t *create_model_logic(const functions_t*);
static	branch_t *copy_model_logic(const branch_t*);
static 	void	parse_model_logic(branch_t*,const char*,int*);
static 	void	print_model_logic(const branch_t*,int,int);
static 	void	expand_logic(branch_t*);
static	double	probe_logic(const branch_t*,const char*);
static 	int	tr_logic(branch_t*);
static 	void	un_logic(branch_t*);
static 	void	ac_logic(branch_t*);
static	struct nodestat *tologic(const struct lmod*,const node_t*);
static	double	toanalog(const struct lmod*,const node_t*);
static	double	tr_review_logic(branch_t*);
/*--------------------------------------------------------------------------*/
extern struct nodestat *nstat;	/* node status flags			    */
extern const int inc_mode;	/* make incremental changes		    */
extern const int sim_phase;
extern const int bypass_ok;
extern const double trtime0;	/* transient analysis time		    */
extern const struct options opt;
extern const struct status stats;

static const char *(typename[]) = {"error", "and", "nand", "or", "nor", "xor",
    "inv", ""};
static struct logic defalt = {(generic_t*)NULL, sizeof(struct logic),
    (struct lmod*)NULL, lDEFAULT_modelname, /*more*/};
static struct lmod defaltmodel = {(generic_t*)NULL, sizeof(struct lmod),
    lDEFAULT_delay, lDEFAULT_rise, lDEFAULT_fall, lDEFAULT_rs,
    lDEFAULT_rw, lDEFAULT_th1, lDEFAULT_th0, lDEFAULT_mr, lDEFAULT_mf,
    lDEFAULT_over, lDEFAULT_vmax, lDEFAULT_vmin, lDEFAULT_range};
static branch_t modellist = {(generic_t*)&defaltmodel, sizeof(branch_t),
    &model_logic, &modellist, &modellist, &modellist, &modellist,
    (branch_t*)NULL, (branch_t*)NULL, lDEFAULT_modelname, /*more*/};
/*--------------------------------------------------------------------------*/
functions_t dev_logic = {
   (generic_t*)&defalt,	/* x */
   sizeof(functions_t),	/* ssize */
   sizeof(branch_t),	/* elementsize */
   (functions_t*)NULL,	/* super */
   PORTSPERGATE, 	/* numnodes */
   rnLOCAL,		/* refnode */
   YES,			/* isdevice */
   create_logic,	/* create */
   copy_logic,		/* copy */
   parse_logic,		/* parse */
   print_logic,		/* print */
   expand_logic,	/* expand */
   probe_logic,		/* probe */
   NULL,		/* tr_probe */
   NULL,		/* ac_probe */
   NULL,		/* xprobe */
   tr_logic,		/* dotr */
   un_logic,		/* untr */
   ac_logic,		/* doac */
   NULL,		/* trfun1 */
   NULL,		/* trfun0 */
   NULL,		/* acfun */
   NULL,		/* tr_guess */
   NULL,		/* tr_advance */
   tr_review_logic	/* tr_review */
};
functions_t model_logic = {
   (generic_t*)&defaltmodel,	/* x */
   sizeof(functions_t),	/* ssize */
   sizeof(branch_t),	/* elementsize */
   (functions_t*)NULL,	/* super */
   0, 			/* numnodes */
   rnMODEL,		/* refnode */
   NO,			/* isdevice */
   create_model_logic,	/* create */
   copy_model_logic,	/* copy */
   parse_model_logic,	/* parse */
   print_model_logic,	/* print */
   NULL,		/* expand */
   NULL,		/* probe */
   NULL,		/* tr_probe */
   NULL,		/* ac_probe */
   NULL,		/* xprobe */
   NULL,		/* dotr */
   NULL,		/* untr */
   NULL,		/* doac */
   NULL,		/* trfun1 */
   NULL,		/* trfun0 */
   NULL,		/* acfun */
   NULL,		/* tr_guess */
   NULL,		/* tr_advance */
   tr_review_logic	/* tr_review */
};
/*--------------------------------------------------------------------------*/
static branch_t *create_logic(const functions_t *func)
{
 branch_t *brh;
 struct logic *x;
 
 brh = create_std(func);
 x = (struct logic*)brh->x;
 brh->n = x->n;
 return brh;
}
/*--------------------------------------------------------------------------*/
static branch_t *copy_logic(const branch_t *proto)
{
 branch_t *brh;
 struct logic *x;
 
 brh = copy_std(proto);
 x = (struct logic*)brh->x;
 brh->n = x->n;
 return brh;
}
/*--------------------------------------------------------------------------*/
static void parse_logic(branch_t *brh, const char *cmd, int *cnt)
{
 struct logic *x;
 x = (struct logic*)brh->x;

 parselabel(brh,cmd,cnt);
 x->incount = parsenodes(brh,cmd,cnt);
 (void)ctostr(cmd, cnt, x->modelname, LABELEN, TOKENTERM);
 if (argparse(cmd,cnt,ONEPASS,
    	"AND",	aENUM,		&x->type,	lAND,
	"NAND",	aENUM,		&x->type,	lNAND,
	"OR",	aENUM,		&x->type,	lOR,
	"NOR",	aENUM,		&x->type,	lNOR,
	"XOR",	aENUM,		&x->type,	lXOR,
	"INV",	aENUM,		&x->type,	lINV,
 	""))
    x->incount -= 2;
 else{
    syntax_check(cmd,cnt,bWARNING);
 }
}
/*--------------------------------------------------------------------------*/
static void print_logic(const branch_t *brh, int where, int detail)
{
 struct logic *x;
 x = (struct logic*)brh->x;

 (void)printlabel(brh,where);
 printnodes(brh,where);
 mprintf(where, " %s %s\n", x->modelname, typename[x->type]); 
}
/*--------------------------------------------------------------------------*/
static branch_t *create_model_logic(const functions_t *func)
{
 branch_t *brh;
 brh = create_std(func);
 brh->stprev = &modellist;
 return brh;
}
/*--------------------------------------------------------------------------*/
static branch_t *copy_model_logic(const branch_t *proto)
{
 branch_t *brh;
 brh = copy_std(proto);
 brh->stprev = &modellist;
 return brh;
}
/*--------------------------------------------------------------------------*/
static void parse_model_logic(branch_t *brh, const char *cmd, int *cnt)
{
 struct lmod *m;
 m = (struct lmod*)brh->x;

 (void)ctostr(cmd, cnt, brh->label, LABELEN, TOKENTERM);
 skiparg(cmd,cnt);	/* skip known "logic" */
 (void)skiplparen(cmd,cnt);
 for (;;){
    if (argparse(cmd,cnt,REPEAT,
	"DElay",aUDOUBLE,	&m->delay,
	"RIse",	aUDOUBLE,	&m->rise,
	"FAll",	aUDOUBLE,	&m->fall,
	"RS",	aUDOUBLE,	&m->rs,
	"RW",	aUDOUBLE,	&m->rw,
	"THH",	aDOUBLE,	&m->th1,
	"THL",	aDOUBLE,	&m->th0,
	"MR",	aUDOUBLE,	&m->mr,
	"MF",	aUDOUBLE,	&m->mf,
	"OVer",	aUDOUBLE,	&m->over,
	"VMAx",	aDOUBLE,	&m->vmax,
	"VMIn",	aDOUBLE,	&m->vmin,
	""))
	;
   else{
      (void)skiprparen(cmd,cnt);
      syntax_check(cmd,cnt,bWARNING);
      break;
   }
 m->range = m->vmax - m->vmin;
 }
}
/*--------------------------------------------------------------------------*/
static void print_model_logic(const branch_t *brh, int where, int detail)
{
 struct lmod *m;
 m = (struct lmod*)brh->x;

 mprintf(where, ".model  %s logic (", brh->label); 
 mprintf(where, " delay=%s ",ftos(m->delay,"", 7, 0));
 mprintf(where, " rise=%s ", ftos(m->rise, "", 7, 0));
 mprintf(where, " fall=%s ", ftos(m->fall, "", 7, 0));
 mprintf(where, " rs=%s ",   ftos(m->rs,   "", 7, 0));
 mprintf(where, " rw=%s ",   ftos(m->rw,   "", 7, 0));
 mprintf(where, " thh=%s ",  ftos(m->th1,  "", 7, 0));
 mprintf(where, " thl=%s ",  ftos(m->th0,  "", 7, 0));
 mprintf(where, " mr=%s ",   ftos(m->mf,   "", 7, 0));
 mprintf(where, " mf=%s ",   ftos(m->mr,   "", 7, 0));
 mprintf(where, " over=%s ", ftos(m->over, "", 7, 0));
 mprintf(where, " vmax=%s ", ftos(m->vmax, "", 7, 0));
 mprintf(where, " vmin=%s ", ftos(m->vmin, "", 7, 0));
 mprintf(where, ")\n");
}
/*--------------------------------------------------------------------------*/
static void expand_logic(branch_t *brh)
{
 char cktname[BUFLEN];
 struct logic *x;

 branch_t *foo;
 foo = brh->subckt;
 
 x = (struct logic*)brh->x;
 sprintf(cktname, "%s%s%u", x->modelname, typename[x->type], x->incount);
 expandgeneric(brh,&modellist);
 brh->subckt = foo;
 expandsubckt(brh,cktname);
 if (!brh->subckt){
    error(bDANGER, "%s: no model, forcing digital\n", printlabel(brh,0));
 }
 brh->tracesubckt = YES;
}
/*--------------------------------------------------------------------------*/
static double probe_logic(const branch_t *brh, const char *what)
{
 struct logic *x;
 double volts;
 int dummy = 0;
 node_t ground;

 ground.e = ground.m = ground.t = 0;
 x = (struct logic*)brh->x;
 if (!x->m  ||  !brh->subckt)
    error(bERROR, "internal error: %s not expanded\n", printlabel(brh,NO));

 volts = tr_volts(&(brh->n[OUT2]),&ground);

 setmatch(what,&dummy);
 if (rematch("V")){
    return volts;
 }else { /* bad parameter */
    return NOT_VALID;
 }
}
/*--------------------------------------------------------------------------*/
static int tr_logic(branch_t *brh)
{
 struct nodestat *n;
 struct logic *x;
 const struct lmod *m;

 x = (struct logic*)brh->x;
 m = x->m;
 n = &(nstat[brh->n[1].m]);

 if (brh->subckt  &&  ((x->gatemode == mANALOG)
 ||  (opt.mode == mMIXED && sim_phase == pINIT_DC)
 ||  (opt.mode == mANALOG))){
    x->gatemode = n->nodemode = mANALOG;
    return brh->converged = tr_fill_rl(brh->subckt);
 }

/* if it gets here, either opt.mode == mMIXED  &&  quality == qGOOD
 *			or opt.mode == mDIGITAL
 */

 brh->bypass = YES;
 x->gatemode = n->nodemode = mDIGITAL;

 if (sim_phase == pINIT_DC){
    (void)tr_review_logic(brh);
    n->finaltime = 0.;
 }

 if (trtime0 >= n->finaltime){	/* in transition, time to propagate */
    brh->bypass = NO;
    n->lv0 = n->lv1;
    n->ls0 = n->ls1;
    n->isanalog = NO;
    n->quality = qGOOD;
    n->diter = stats.iter[iTOTAL];
    n->finaltime = BIGBIG;
    n->lastchange = trtime0;
 }

 if (n->finaltime != BIGBIG  ||  !brh->bypass  ||  !inc_mode){
    node_t vdd;
    node_t ground;
    vdd = brh->n[OUT1];
    ground.e = ground.m = ground.t = 0;
    brh->n[OUT1] = ground;
    /* trsetup(brh); done by caller.  BUG???? need to test!!! */
    brh->m0.x = 0.;
    brh->y0.x = 0.;
    brh->y0.f1 = -toanalog(m,&(brh->n[1]));
    brh->y0.f0 = 0.;
    brh->m0.f1 = 1./m->rs;
    brh->m0.c0 = brh->y0.f1 / -m->rs;
    trloadpassive(brh);
    brh->n[OUT1] = vdd;
 }
 return brh->converged = YES;
}
/*--------------------------------------------------------------------------*/
static void un_logic(branch_t *brh)
{
 tr_unfill_rl(brh->subckt);
 unloadpassive(brh);
}
/*--------------------------------------------------------------------------*/
static void ac_logic(branch_t *brh)
{
 error(bWARNING, "%s: no logic in AC analysis\n", printlabel(brh,NO));
}
/*--------------------------------------------------------------------------*/
static struct nodestat *tologic(const struct lmod *m, const node_t *node)
{
 struct nodestat *n;

 if (node->m == INVALIDNODE){
    node_t ground;
    ground.e = ground.m = ground.t = 0;
    error(bDANGER, "%u:(%u,%u,%u):internal error: invalid node\n",
	  		stats.iter[iTOTAL], node->e, node->t, node->m);
    return &(nstat[ground.m]);
 }
 
 n = &(nstat[node->m]);
 if (!m){
    m = n->family;
 }else if (!n->family){
    n->family = m;
 }
 
 if (n->nodemode == mDIGITAL){
    /* nothing */;
 }else{ /* n->nodemode == mANALOG */
    if (n->diter < n->aiter){
       double newv;
       double sv;
       int oldstate;
       node_t ground;
       
       ground.e = ground.m = ground.t = 0;
       newv = tr_volts(node,&ground);
       sv = newv / m->range;
       
       n->dt = trtime0 - n->lastchange;
       oldstate = n->lv0;

       if (sv >= m->th1){
          n->lv0 = n->lv1 = YES;
       }else if (sv <= m->th0){
          n->lv0 = n->lv1 = NO;
       }else{					/* transition region */
          int lv1old = n->lv1;
          double oldv = tr_volts_t1(node,&ground);
	  double oldsv = oldv / m->range;
	  double diff = sv - oldsv;
	  
	  if (diff > opt.abstol){			    /* rising */
	     n->lv1 = YES;
	     if ((n->lv1 == lv1old)
	     &&  (n->lv0 == n->lv1  ||  diff < n->dt/(m->mr * m->rise))){
	        n->quality = qBAD;		/* inflection or too slow */
		n->failuremode = "slow rise";
	     }
	  }else if (diff < -opt.abstol){		    /* falling */
	     n->lv1 = NO;
	     if ((n->lv1 == lv1old)
	     &&  (n->lv0 == n->lv1  ||  -diff < n->dt/(m->mf * m->fall))){
	        n->quality = qBAD;		/* inflection or too slow */
		n->failuremode = "slow fall";
	     }
	  }
       }
       if (sv > 1.+m->over  ||  sv < -m->over){	/* out of range */
          n->quality = qBAD;
	  n->failuremode = "out of range";
       }
       if (n->quality != qGOOD  &&  n->lv0 != oldstate){
          ++n->quality;
       }
       n->family = m;
       n->diter = stats.iter[iTOTAL];
       n->lastchange = trtime0;
    }else{
    }
 }
 if (m != n->family){
    n->quality = qBAD;
    n->failuremode = "family mismatch";
 } 
 return n;
}
/*--------------------------------------------------------------------------*/
static double toanalog(const struct lmod *m, const node_t *node)
{
 struct nodestat *n;

 n = &(nstat[node->m]);
 if (!m){
    m = n->family;
 }else if (!n->family){
    n->family = m;
 }

 if (n->lv0 == n->lv1){
    return (n->lv0) ? m->vmax : m->vmin;
 }else if (trtime0 <= (n->finaltime - ((n->lv0)?(m->fall):(m->rise)))){
    return (n->lv0) ? m->vmax : m->vmin;
 }else if (trtime0 >= n->finaltime){    
    return (n->lv1) ? m->vmax : m->vmin;
 }else{
    double start,end,interp;
    start = (n->lv0) ? m->vmax : m->vmin;
    end   = (n->lv1) ? m->vmax : m->vmin;
    interp = (n->finaltime-trtime0) / ((n->lv0)?(m->fall):(m->rise));
    return end - (end - start) * interp;
 }
}
/*--------------------------------------------------------------------------*/
static double tr_review_logic(branch_t *brh)
{
 struct nodestat *n[PORTSPERGATE];
 struct logic *x;
 const struct lmod *m;
 int count;			/* review */
 int lastchangenode = 0;	/* review */
 int lastchangeiter = 0;	/* review, once */
 int quality = qGOOD;		/* both */
 const char* failuremode = "default";	/* review & print */

 x = (struct logic*)brh->x;
 m = x->m;

 for (count = 1;
      count < PORTSPERGATE  &&  brh->n[count].e != INVALIDNODE;
 /**/ count++){
    n[count] = tologic(m,&(brh->n[count]));
    if (quality > n[count]->quality){
       quality = n[count]->quality;
       failuremode = n[count]->failuremode;
    }
    if (n[count]->diter >= lastchangeiter){
       lastchangeiter = n[count]->diter;
       lastchangenode = count;
    }
 }
 
 /* count == number of connections, now const */
 /* if lastchangenode == 1, no new changes, bypass */

 if (brh->subckt  &&  
 ((opt.mode == mANALOG)  ||  (opt.mode == mMIXED && quality != qGOOD))){
    if (x->gatemode == mDIGITAL){
       error(bTRACE, "%s:%u:%g switch to analog (review), %s\n",
       		printlabel(brh,0), stats.iter[iTOTAL], trtime0, failuremode);
       unloadpassive(brh);
    }
    x->gatemode = nstat[brh->n[1].m].nodemode = mANALOG;
    return tr_review_rl(brh->subckt);
 }

/* if it gets here, either opt.mode == mMIXED  &&  quality == qGOOD
 *			or opt.mode == mDIGITAL
 */

 if (x->gatemode == mANALOG){
    error(bTRACE, "%s:%u:%g switch to digital (review)\n",
    		printlabel(brh,0), stats.iter[iTOTAL], trtime0);
    tr_unfill_rl(brh->subckt);
 }
 x->gatemode = n[1]->nodemode = mDIGITAL;
 brh->bypass = YES;

 if (!bypass_ok || lastchangenode!=1 || sim_phase==pINIT_DC){
    struct nodestat out;
    int ii;
    out = *n[1];
    brh->bypass = NO;
    switch (x->type){
    case lAND:
       out.lv1 = YES;
       for (ii = 2;  ii < count;  ii++)
             out.lv1 &= n[ii]->lv0;
       break;
    case lNAND:
       out.lv1 = YES;
       for (ii = 2;  ii < count;  ii++)
             out.lv1 &= n[ii]->lv0;
       out.lv1 = !out.lv1;
       break;
    case lOR:
       out.lv1 = NO;
       for (ii = 2;  ii < count;  ii++)
             out.lv1 |= n[ii]->lv0;
       break;
    case lNOR:
       out.lv1 = NO;
       for (ii = 2;  ii < count;  ii++)
             out.lv1 |= n[ii]->lv0;
       out.lv1 = !out.lv1;
       break;
    case lXOR:
       out.lv1 = n[2]->lv0 ^ n[3]->lv0;
       break;
    case lINV:
       out.lv1 = !n[2]->lv0;
       break;
    default:
       error(bWARNING,"%s: %s, bad logic type\n",printlabel(brh,NO),x->type);
       break;
    }
    if (out.lv1 != n[1]->lv1){
       if (out.finaltime != BIGBIG){ 			/* in transition */
	  out.quality = qBAD;
	  out.failuremode = "race";
       }
       out.diter = stats.iter[iTOTAL];
       out.finaltime = trtime0 + m->delay;
       error(bTRACE, "%s:%u:%g new event\n",
    		printlabel(brh,0), stats.iter[iTOTAL], trtime0);
       new_event(out.finaltime);
       out.lastchange = trtime0;
       *n[1] = out;
       if (lastchangenode == 1){
          error(bDANGER, "%s:%u:%g non-event state change\n",
	  	printlabel(brh,0), stats.iter[iTOTAL], trtime0);
       }
    }else if (lastchangenode != 1){
       error(bTRACE,"%s:%u:%g null transition\n",
       		printlabel(brh,0), stats.iter[iTOTAL], trtime0);
    }else{
       error(bTRACE,"%s:%u:%g null evaluation\n",
       		printlabel(brh,0), stats.iter[iTOTAL], trtime0);
    }
 }
 return BIGBIG;
}
/* ***BUG*** review and advance need to be separated.
 * If a step is rejected by review, the logic values could become corrupt.
 */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
