/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "gsim.h"
#include "memory.h"

void init_process();
void init_and();
void init_or();
void init_xor();
void init_buf();
void init_switch();
void init_supply();
void init_bufif();
void init_nmos();
void init_pmos();
void init_add();
void init_clock();
void init_register();
void init_flipflop();
void init_mux();
void init_demux();
void init_concat();
void init_mult();
void init_div();
void init_ram();
void init_rom();
void init_lshift();
void init_rshift();
void init_arshift();
void init_roll();
void init_tty();
void init_tap();
void init_led();

static SHash gate_types;

static SGateInfo ignore = {
  GT_IGNORE, "ignored"
};

static char *ignored_types[] = {
  "input","output","inout","joint", "comment", "frame"
}; 


static unsigned spread(unsigned M,unsigned x)
{
  unsigned R = 0;
  int i,j;

  j = 0;
  for (i = 0;i < 32;i++) 
    if ((M & (1 << i)) && (x & (1 << j++)))
      R |= (1 << i);

  return R;
}

/*
 * Register new gate types and do some pre-processing.
 */
void SGateInfo_register(SGateInfo *gi,const char *name)
{
  static int code = GT_IGNORE+1;
  int i;

  if (gi->gi_code <= 0)
    gi->gi_code = code++;

  if (name)
    SHash_insert(&gate_types,name,gi);
  else {
    char buf[STRMAX],*T;
    strcpy(buf,gi->gi_name);
    for (T = strtok(buf,":");T;T = strtok(0,":"))
      SHash_insert(&gate_types,T,gi);
  }

  /*
   * Initialize pad numbers
   */
  for (i = 0;i < gi->gi_numPads;i++)
    gi->gi_pad[i].idx = i;
}

int gate_properties_lookup(const char *func,const char *name,void *data,int *rval)
{
  SGate *g = (SGate*) data;
  int l = strlen(name);
  int b = 0, n = 0, c = 0;
  int i;

  if (!func) return EE_BADFUNC;
  if (!name) return EE_NOTDEF;

  for (i = 0;i < g->g_ports.num;i++) {
    SPort *P = g->g_ports.port[i];

    if (strncmp(P->p_name,name,l) == 0 && (!P->p_name[l] || isdigit(P->p_name[l]))) {
      if (P->p_net->n_nbits > b) b = P->p_net->n_nbits;
      if (P->p_comp) c++;
      n++;
    }
  }

  if (n == 0) {
    expr_errsym = name;
    return EE_NOTDEF;
  }

  if (strcmp(func,"bits") == 0) {
    *rval = b;
    return 0;
  } else if (strcmp(func,"inv") == 0) {
    *rval = c;
    return 0;
  } else if (strcmp(func,"num") == 0) {
    *rval = n;
    return 0;
  } else {
    expr_errsym = func;
    return EE_BADFUNC;
  }

  return 0;
}

void delayErrorMessage(int rr,SGate *g,const char *dname)
{
  switch (rr) {
  case EE_NOTDEF :
    errorGate(g->g_name,"Undefined literal '%s' in %s<%s> gate delay expression (check parameters file)",
	      expr_errsym,g->g_typeName,dname);
    break;
  case EE_BADFUNC :
    errorGate(g->g_name,"Illegal function name '%s' in %s<%s> gate delay expression (check parameters file)",
	      expr_errsym,g->g_typeName,dname);
    break;
  case EE_DIV0 :
    errorGate(g->g_name,"Division by zero computing %s<%s> gate delay (check parameters file)",
	      g->g_typeName,dname);
    break;
  default :
    errorGate(g->g_name,"Can not evaluate (code %d) on %s<%s> gate delay expression (check parameters file)",
	      rr,g->g_typeName,dname);
    break;
  }
}

void areaPowerErrorMessage(int rr,SGate *g,const char *what)
{
  switch (rr) {
  case EE_NOTDEF :
    errorGate(g->g_name,"Undefined literal '%s' in %s gate %s expression (check parameters file)",
	      expr_errsym,g->g_typeName,what);
    break;
  case EE_BADFUNC :
    errorGate(g->g_name,"Illegal function name '%s' in %s gate %s expression (check parameters file)",
	      expr_errsym,g->g_typeName,what);
    break;
  case EE_DIV0 :
    errorGate(g->g_name,"Division by zero computing %s gate %s (check parameters file)",
	      g->g_typeName,what);
    break;
  default :
    errorGate(g->g_name,"Can not evaluate (code %d) on %s gate %s expression (check parameters file)",
	      rr,g->g_typeName,what);
    break;
  }
}

/*
  Set the delay (also area and power) parameters for a gate.
 */
void setGateDelayParms(SGate *g)
{
  SGateInfo *gi = g->g_type;
  GDelayDef *dd;
  int i,j,n;
  int rr;
  char buf[STRMAX],*p;

  if (!gi) return;

  strcpy(buf,gi->gi_name);
  p = strchr(buf,':');
  if (p) *p = 0;
  dd = GDelayDef_find(g->g_tech,buf);
  if (!dd) {
    /*
      Missing delay definition is an error if the gate has at least one error parameter.
     */
    if (gi->gi_delayNames[0].ds_name)
      errorGate(g->g_name,"No delay parameters specified.");
    return;
  }

  if (dd) {
    if (!(rr = Expr_eval(dd->dd_area,&n,gate_properties_lookup,g))) {
      g->g_area = n;
      if (n < 0)
	errorGate(g->g_name,"Area %d on '%s' gate is negative (check parameters file)",n,g->g_typeName);
      total_area += n;
    } else
      areaPowerErrorMessage(rr,g,"area");

    if (!(rr = Expr_eval(dd->dd_power,&n,gate_properties_lookup,g))) {
      g->g_staticPower = n;
      total_staticPower += n;
      if (n < 0)
	errorGate(g->g_name,"Power %d on '%s' gate is negative (check parameters file)",n,g->g_typeName);
    } else
      areaPowerErrorMessage(rr,g,"power");
  }

   /*
   * Set delay parameters based on technology if not already set as custom delay.
   */
  if (!g->g_delayParms) {
    /*
    Compute the length of and allocate the gate delay array.
   */
    n = 0;
    for (i = 0;gi->gi_delayNames[i].ds_name;i++)
      n++;
    if (n == 0) n = 1;


    g->g_delayParms = (int*) malloc(sizeof(int)*n);

  /* Initialize all delays to '1' in case we fail to find a real delay value */
    for (i = 0;gi->gi_delayNames[i].ds_name;i++) {
      g->g_delayParms[i] = 1;
    }

    for (i = 0;gi->gi_delayNames[i].ds_name;i++) {
      int delay = 1;

      for (j = 0;j < dd->dd_numDelays;j++)
	if (strcmp(gi->gi_delayNames[i].ds_name,dd->dd_names[j]) == 0) break;
      if (j >= dd->dd_numDelays) {
	for (j = 0;j < dd->dd_numDelays;j++)
	  if (strcmp(dd->dd_names[j],"*") == 0) break;
      }
      if (j >= dd->dd_numDelays) {
	errorGate(g->g_name,"Can't find %s<%s> delay value (check parameters file)",
		  g->g_typeName,gi->gi_delayNames[i].ds_name);
	continue;
      }

      rr = Expr_eval(dd->dd_delay[i],&delay,gate_properties_lookup,g);
      if (rr) {
	delayErrorMessage(rr,g,gi->gi_delayNames[i].ds_name);
	continue;
      }
      g->g_delayParms[i] = delay;
    }
  }

  /*
    Go through delays again to check for bogus values
   */
  for (i = 0;gi->gi_delayNames[i].ds_name;i++) {
    int delay = g->g_delayParms[i];

    if (delay <= 0) {
      errorGate(g->g_name,"Delay on %s<%s> is non-positive (check parameters file)",
		g->g_typeName,gi->gi_delayNames[i]);
      delay = 1;
    }
    if (delay >= THYMEWHEEL_SIZE) {
      errorGate(g->g_name,"Delay of %d on %s<%s> is too large (check parameters file)",
		delay,g->g_typeName,gi->gi_delayNames[i]);
      delay = 1;
    }
    g->g_delayParms[i] = delay;
#if 0
    logMsg("delay %s<%s> on %s is %d (tech=%s : %s)",g->g_typeName,gi->gi_delayNames[i],g->g_name,delay,g->g_tech,dd->dd_tech);
#endif
  }
}

SGateInfo *SGateInfo_find(const char *name)
{
  return (SGateInfo*) SHash_find(&gate_types,name); 
}

unsigned SGateInfo_compMask(SGateInfo *gi,const char *name)
{
  char buf[STRMAX],*T;
  int p;

  strcpy(buf,gi->gi_name);
  for (p = 0,T = strtok(buf,":");T;p++, T = strtok(0,":"))
    if (strcmp(name,T) == 0)
      return spread(gi->gi_vmask,p);
  return 0;
}

/*
   gate types which are to be ignored.
*/
static void init_ignored()
{
  int i;
  int N = sizeof(ignored_types)/sizeof(ignored_types[0]);

  for (i = 0;i < N;i++)
    SGateInfo_register(&ignore,ignored_types[i]);
}

int SGateInfo_portRank(SGateInfo *gi,const char *pname)
{
  char buf[STRMAX];
  int p;
  int i;

  if (sscanf(pname,"%[A-Za-z_]%d",buf,&p) < 2)
    p = 0;

  for (i = 0;i < gi->gi_numPads;i++)
    if (strcmp(buf,gi->gi_pad[i].name) == 0)
      return i*MAXPADPINS + p;
  return -1;
}

int Generic_checkGate(SGate *g)
{
  int ob,i;

  if (g->g_ports.num < 2) {
    errorGate(g->g_name,"Too few pins on %s gate.",g->g_type->gi_name);
    return -1;
  }

  ob = g->g_ports.port[0]->p_net->n_nbits;

  if (g->g_ports.num == 2) {	/* This is a reduction gate */
    if (ob != 1) {
      errorGate(g->g_name,"Output on reduction gates should be single bit.");
      return -1;
    }
  } else {
    for (i = 1;i < g->g_ports.num;i++) {
      int ib = g->g_ports.port[i]->p_net->n_nbits;
      if (ib != 1 && ib != ob) {
	errorGate(g->g_name,
		  "Inputs on %s gates must be single bit"
		  " or match output bit width",g->g_type->gi_name);
	return -1;
      }
    }
  }
  return 0;
}

SGate *Generic_copyGate(SGate *sg,const char *name,SModule *M)
{
  SGate *ng;

  ng = new_SGate(sg->g_typeName,name);
  ng->g_source = sg;
  ng->g_type = sg->g_type;
  ng->g_tech = sg->g_tech;
  ng->g_flags = sg->g_flags;
  ng->g_delayParms = sg->g_delayParms;
  SModule_addGate(M,ng);

  return ng;
}

void Nop_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
}

int Nop_checkGate(SGate *g)
{
  return 0;
}

void Nop_initGate(EvQueue *Q,SGate *g)
{
}

void Generic_setProp(SGate *g,const char *name,const void *value)
{
#if 0
  logMsg("prop: %s",name);
#endif
}

/*
 * Return the delay from port Pfrom to port Pto.  This can be
 * either a 'forward' or a 'backward' delay.  Return NO_TIME,
 * if there is no meaningful delay value between the ports.
 *
 * If Pto is null, then this is a request for an internal
 * delay at a port.  The default internal delay is 0.
 */
simTime	Generic_delay(SPort *Pfrom,SPort *Pto)
{
  int s_idx, d_idx;
  SGate *g = Pfrom->p_gate;
  SGateInfo *gi = g->g_type;
  simTime t = NO_TIME;
  int i;

  if (!Pto) return 0;

  s_idx = SPort_getPadIdx(Pfrom);
  d_idx = SPort_getPadIdx(Pto);

  assert(Pfrom->p_gate == Pto->p_gate);

  for (i = 0;gi->gi_delayNames[i].ds_name;i++) {
    SDelaySpec *ds = &gi->gi_delayNames[i];

    if ((ds->ds_from & (1<<s_idx)) != 0 && ds->ds_to == d_idx) {
      t = g->g_delayParms[i];
      break;
    }
    if ((ds->ds_from & (1<<d_idx)) != 0 && ds->ds_to == s_idx) {
      t = g->g_delayParms[i];
      break;
    }
  }

  /*  sendMsg("echo delay %s[%s(%d) -> %s(%d)] = %d",g->g_name,Pfrom->p_name,s_idx,Pto->p_name,d_idx,t);*/

  return t;
}

void Generic_propFrwdDelay(SPort *P,simTime t)
{
  SGate *g = P->p_gate;
  int k;

  if (SPort_effectiveType(P) != GIO_IN) return;
  if (t <= P->p_frwdDelay) return;

  P->p_frwdDelay = t;

  /*  sendMsg("echo propFrwd %s[%s]: %d",g->g_name,P->p_name,t); */

  for (k = 0;k < g->g_ports.num;k++) {
    SPort *P2 = g->g_ports.port[k];
    simTime d;

    if (SPort_effectiveType(P2) != GIO_OUT) continue;
    if (g->g_type->gi_delay) {
      d = (*g->g_type->gi_delay)(P,P2);
    } else {
      d = NO_TIME;
    }

    if (d == NO_TIME) continue;
    SNet_propFrwdDelay(P2->p_net,d+t);
  }
}

void Generic_propBackDelay(SPort *P,simTime t)
{
  SGate *g = P->p_gate;
  int k;

  if (SPort_effectiveType(P) != GIO_OUT) return;
  if (t <= P->p_backDelay) return;

  P->p_backDelay = t;

  for (k = 0;k < g->g_ports.num;k++) {
    SPort *P2 = g->g_ports.port[k];
    simTime d;

    if (SPort_effectiveType(P2) != GIO_IN) continue;
    d = g->g_type->gi_delay ? (*g->g_type->gi_delay)(P,P2) : NO_TIME;
    if (d == NO_TIME) continue;
    SNet_propBackDelay(P2->p_net,d+t);
  }
}

void init_gates()
{
  SHash_init(&gate_types);

  init_ignored();
  init_process();

  init_and();
  init_or();
  init_xor();
  init_buf();
  init_switch();
  init_supply();
  init_bufif();
  init_nmos();
  init_pmos();
  init_add();
  init_clock();
  init_register();
  init_flipflop();
  init_mux();
  init_demux();
  init_concat();
  init_mult();
  init_div();
  init_ram();
  init_rom();
  init_lshift();
  init_rshift();
  init_arshift();
  init_roll();
  init_tty();
  init_tap();
  init_led();
}
