/****************************************************************************
    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 "hash.h"
#include "gsim.h"

#define PORTLIST_STEP		8

extern SHash module_table;

void Concat_setDirections(SModule *M);
void setGateDelayParms(SGate*);

SModule *new_SModule(const char *name)
{
  SModule *M = (SModule*) malloc(sizeof(SModule));

  M->m_name = strdup(name);
  SPortList_init(&M->m_ports);
  SHash_init(&M->m_nets);
  SHash_init(&M->m_gates);

  return M;
}

void SModule_addPort(SModule *M,SNet *N)
{
  SPortList_add(&M->m_ports,new_SPort(N->n_name,N,0));
}

SNet *SModule_findAddNet(SModule *M,const char *net)
{
  SNet *N = (SNet*)SHash_find(&M->m_nets,net);

  if (!N) {
    N = new_SNet(net,1);
    SHash_insert(&M->m_nets,N->n_name,N);
  }
  return N;
}

SNet *SModule_findNet(SModule *M,const char *net)
{
  SNet *N = (SNet*)SHash_find(&M->m_nets,net);
  return N;
}

SGate *SModule_findGate(SModule *M,const char *gat)
{
  SGate *G = (SGate*)SHash_find(&M->m_gates,gat);
  return G;
}

void SModule_addGate(SModule *M,SGate *g)
{
  SHash_insert(&M->m_gates,g->g_name,g);
}

static void SModule_printNets(SModule *M,FILE *f)
{
  HashElem *E;
  int C;

  C = 0;
  for (E = Hash_first(&M->m_nets);E;E = Hash_next(&M->m_nets,E)) {
    SNet *N = (SNet*) HashElem_obj(E);

    if (C && (C + strlen(N->n_name) > 75)) {
      fprintf(f,";\n");
      C = 0;
    }
    if (C == 0) {
      fprintf(f,"wire ");
      C += 5;
    } else {
      fprintf(f,", ");
      C += 2;
    }
    if (N->n_merge)
      fprintf(f,"(%s)",N->n_name);
    else
      fprintf(f,"%s",N->n_name);
    C += strlen(N->n_name);
  }
  if (C != 0)
    fprintf(f,";\n");
  fprintf(f,"\n");
}

static void SModule_printGates(SModule *M,FILE *f)
{
  HashElem *E;

  for (E = Hash_first(&M->m_gates);E;E = Hash_next(&M->m_gates,E)) {
    SGate *g = (SGate*) HashElem_obj(E);

    SGate_print(g,f);
  }
  fprintf(f,"\n");
}

void SModule_print(SModule *M,FILE *f)
{
  fprintf(f,"module %s",M->m_name);
  if (M->m_ports.num > 0) {
    int i;
    fprintf(f,"(");
    for (i = 0;i < M->m_ports.num;i++) {
      if (i != 0) fprintf(f,", ");
      fprintf(f,"%s",M->m_ports.port[i]->p_name);
    }
    fprintf(f,")");
  }
  fprintf(f,";\n");

  SModule_printNets(M,f);
  SModule_printGates(M,f);
    
  fprintf(f,"endmodule\n\n");
}

void SModule_mergeNets(SModule *M,SNet *N,SNet *SN)
{
  SN->n_merge = N;
}

/*
   Helping function to marge nets at module boundaries.
*/
static void SModule_attachPorts(SModule *M,const char *root_name,SGate *g,SHash *netTable,SHash *portTable)
{
  int i;

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

    if (netTable) {
      N = (SNet*) SHash_find(netTable,P->p_net->n_name);
      assert(N);
    } else
      N = P->p_net;
    SN = (SNet*) SHash_find(portTable,P->p_name);
    if (SN) {
      if (N->n_nbits != SN->n_nbits) {
	char buf[STRMAX];

	if (SGate_isErrMarked(g)) continue;
	if (*root_name)
	  sprintf(buf,"%s.%s",root_name,g->g_name);
	else
	  strcpy(buf,g->g_name);
	errorGate(buf,"Expected bit width of %d for port '%s'.",SN->n_nbits,P->p_name);
	SGate_errMark(g);
      }
      SModule_mergeNets(M,N,SN);
    } else {
      char buf[STRMAX];

      if (SGate_isErrMarked(g)) continue;

      if (*root_name)
	sprintf(buf,"%s.%s",root_name,g->g_name);
      else
	strcpy(buf,g->g_name);
      errorGate(buf,"Pin '%s' on module is not defined.",P->p_name);
      SGate_errMark(g);
    }
  }
}

void SModule_expandSubMod(SModule *M,SModule *SM,SGate *g,const char *root_name,SHash *portTable)
{
  char buf[STRMAX];
  HashElem *E;
  int i;
  SHash netTable;

  SHash_init(&netTable);

  /*
     For each net in submodule SM, create a net in the expand module
     by appending the net name in SM to the root name. 
   */
  for (E = Hash_first(&SM->m_nets);E;E = Hash_next(&SM->m_nets,E)) {
    SNet *sN = (SNet*) HashElem_obj(E);
    SNet *N;

    sprintf(buf,"%s.%s",root_name,sN->n_name);
    N = SModule_findAddNet(M,buf);
    N->n_source = sN;
    N->n_nbits = sN->n_nbits;
    SHash_insert(&netTable,sN->n_name,N);
  }

  /*
     Create the port table for SM.  The port table maps port names to nets
     in the current instance of SM.
   */
  for (i = 0;i < SM->m_ports.num;i++) {
    SPort *P = SM->m_ports.port[i];
    SNet *N;

    sprintf(buf,"%s.%s",root_name,P->p_name);
    N = SModule_findAddNet(M,buf);
    SHash_insert(portTable,P->p_name,N);
  }

  /*
     For each gate submodule SM, create a gate in the expand module
     by appending the gate name in SM to the root name.  Attach wires
     in the new gate to the corresponding new nets.
   */
  for (E = Hash_first(&SM->m_gates);E;E = Hash_next(&SM->m_gates,E)) {
    SGate *sg = (SGate*) HashElem_obj(E);
    SGateInfo *gi = sg->g_type;

    sprintf(buf,"%s.%s",root_name,sg->g_name);

    if (gi) {	/* This is a regular gate. */
      SGate *ng = (*sg->g_type->gi_copyGate)(sg,buf,M);

      for (i = 0;i < sg->g_ports.num;i++) {
	SNet *N;
    
	N = (SNet*) SHash_find(&netTable,sg->g_ports.port[i]->p_net->n_name);
	SGate_addPort(ng,sg->g_ports.port[i]->p_name,N
		      ,sg->g_ports.port[i]->p_comp);
      }
    } else {	/* This is a submodule. */
      SModule *ssM = (SModule*) SHash_find(&module_table,sg->g_typeName);
      SHash portTable;

      SHash_init(&portTable);
      if (!ssM) {
	errorGate(sg->g_name,"Called module '%s' undefined.",sg->g_typeName);
	continue;
      }
      SModule_expandSubMod(M,ssM,sg,buf,&portTable);
      SModule_attachPorts(M,root_name,sg,&netTable,&portTable);
      SHash_uninit(&portTable);
    }
  }

  SHash_uninit(&netTable);
}

/*
   Expand sub-modules in top-level module M to create a single flat module.
*/
void SModule_expand(SModule *M)
{
  HashElem *E;
  SHash mod_set;		/* Set of expanded modules */

  SHash_init(&mod_set);
  for (E = Hash_first(&M->m_gates);E;E = Hash_next(&M->m_gates,E)) {
    SGate *g = (SGate*) HashElem_obj(E);
    SModule *SM;

    if (g->g_type) continue;
    SM = (SModule*) SHash_find(&module_table,g->g_typeName);
    if (!SM) {
      errorGate(g->g_name,"Called module '%s' undefined.",g->g_typeName);
      continue;
    }
    SHash_insert(&mod_set,g->g_name,g);
  }

  for (E = Hash_first(&mod_set);E;E = Hash_next(&mod_set,E)) {
    SGate *g = (SGate*) HashElem_obj(E);
    SModule *SM;
    SHash portTable;

    SM = (SModule*) SHash_find(&module_table,g->g_typeName);
    SHash_init(&portTable);
    SModule_expandSubMod(M,SM,g,g->g_name,&portTable);
    SModule_attachPorts(M,"",g,0,&portTable);
    SHash_uninit(&portTable);
  }

  for (E = Hash_first(&mod_set);E;E = Hash_next(&mod_set,E)) {
    SGate *g = (SGate*) HashElem_obj(E);
    SHash_remove(&M->m_gates,g->g_name);
  }

  SHash_uninit(&mod_set);
}

void SModule_finalize(SModule *M)
{
  HashElem *E;

  for (E = Hash_first(&M->m_gates);E;E = Hash_next(&M->m_gates,E)) {
    SGate *g = (SGate*) HashElem_obj(E);
    SGate_finalize(g);
  }

  for (E = Hash_first(&M->m_nets);E;E = Hash_next(&M->m_nets,E)) {
    SNet *N = (SNet*) HashElem_obj(E);

    SState_init(&N->n_state,N->n_nbits);
    SState_init(&N->n_oldState,N->n_nbits);
    SState_unknown(&N->n_state);
    SState_unknown(&N->n_oldState);
  }

  Concat_setDirections(M);
}

SGate *new_SGate(const char *gtype,const char *gname)
{
  SGate *g = (SGate*)malloc(sizeof(SGate));

  g->g_type = 0;
  g->g_typeName = strdup(gtype);
  g->g_name = strdup(gname);
  g->g_flags = 0;
  g->g_source = 0;
  SPortList_init(&g->g_ports);
  g->g_data = 0;
  g->g_delayParms = 0;
  g->g_area = 0;
  g->g_staticPower = 0;
  g->g_tech = strdup("default");

  return g;
}

void SGate_addPort(SGate *g,const char *pname,SNet *N,int comp)
{
  if (!pname && g->g_type) {
    SGateInfo *gi = g->g_type;
    int i = g->g_ports.num;
    if (i >= gi->gi_numPads)
      i = gi->gi_numPads-1;
    pname = g->g_type->gi_pad[i].name;
  }
  SPortList_add(&g->g_ports,new_SPort(pname,N,comp));

#if 0
#define XXX(p) ((p) ? (p)->p_net->n_name : "-")
  sendMsg("echo ATTACH(%p) Z=%s  I=%s  G=%s\n",g,
	  XXX(g->g_ports.port[0]),
	  XXX(g->g_ports.port[1]),
	  XXX(g->g_ports.port[2]));
#endif

}

static SGateInfo *gpc_type = 0;
static int gate_port_compare(const void *vA,const void *vB)
{
  SPort *A = *(SPort**)vA;
  SPort *B = *(SPort**)vB;

  return SGateInfo_portRank(gpc_type,A->p_name)
    - SGateInfo_portRank(gpc_type,B->p_name);
}


void SGate_sortPins(SGate *g)
{
  gpc_type = g->g_type;
  qsort(g->g_ports.port,g->g_ports.num,sizeof(SPort*),gate_port_compare);
}

void SGate_finalize(SGate *g)
{
  int i;
  NHash nets;

  NHash_init(&nets);
  for (i = 0;i < g->g_ports.num;i++) {
    SPort *P = g->g_ports.port[i];
    P->p_net = SNet_canonical(P->p_net);
    P->p_gate = g;
    P->p_gpos = i;
    P->p_isDup = 0;

    if (g->g_type) {
      if (i < g->g_type->gi_numPads)
	P->p_type = &g->g_type->gi_pad[i];
      else
	P->p_type = &g->g_type->gi_pad[g->g_type->gi_numPads-1];
      SNet_attachPort(P->p_net,P);
    }

    if (NHash_find(&nets,(nkey_t)P->p_net))
      P->p_isDup = 1;
    else
      NHash_insert(&nets,(nkey_t)P->p_net,P->p_net);

    SState_init(&P->p_state,P->p_net->n_nbits);
    SState_unknown(&P->p_state);

    SState_init(&P->p_qstate,P->p_net->n_nbits);
    SState_unknown(&P->p_qstate);
  }
  NHash_uninit(&nets);

  setGateDelayParms(g);
}

void SGate_print(SGate *g,FILE *f)
{
  int i;

  fprintf(f,"   %s %s (",g->g_typeName,g->g_name);
  for (i = 0;i < g->g_ports.num;i++) {
    SPort *P = g->g_ports.port[i];

    if (i != 0) fprintf(f,", ");
    fprintf(f,".%s(%s)",P->p_name,P->p_net->n_name);
  }

  fprintf(f,");\n");
}

void SGate_errMark(SGate *g)
{
  if (g->g_source)
    g = g->g_source;
  g->g_flags |= GF_ERRREP;
}

int SGate_isErrMarked(SGate *g)
{
  if (g->g_source)
    g = g->g_source;
  return (g->g_flags & GF_ERRREP) != 0;
}

SState *SGate_allocPortState(SGate *g,int p)
{
  SState *pS = &g->g_ports.port[p]->p_net->n_state;
  SState *cS = alloc_SState();

  SState_reinit(cS,pS->nbits);
  SState_copy(cS,pS);
  if (g->g_ports.port[p]->p_comp)
    SState_not(cS,cS);
  
  return cS;
}


SPort *new_SPort(const char *name,SNet *N,int comp)
{
  SPort *P = (SPort*)malloc(sizeof(SPort));

  P->p_name = name ? strdup(name) : 0;
  P->p_comp = comp;
  P->p_net = N;
  P->p_gate = 0;
  P->p_gpos = -1;
  P->p_nedge = NO_TIME;
  P->p_ledge = NO_TIME;
  P->p_dynamicPower = 0;
  return P;
}

SNet *new_SNet(const char *name,int nbits)
{
  SNet *N = (SNet*)malloc(sizeof(SNet));

  N->n_name = strdup(name);
  N->n_nbits = nbits;
  N->n_flags = 0;
  N->n_merge = 0;
  N->n_watchNames = 0;
  N->n_breaks = 0;
  N->n_source = 0;
  SPortList_init(&N->n_ports);

  return N;
}

void SNet_setNBits(SNet *N,int nbits)
{
  N->n_nbits = nbits;
}

void SNet_attachPort(SNet *N,SPort *P)
{
  int i;

  for (i = 0;i < N->n_ports.num;i++)
    if (N->n_ports.port[i] == P)
      return;
  SPortList_add(&N->n_ports,P);
}

SNet *SNet_canonical(SNet *n)
{
  while (n->n_merge) n = n->n_merge;
  return n;
}

void SNet_errMark(SNet *n)
{
  if (n->n_source)
    n = n->n_source;
  n->n_flags |= NF_ERRREP;
}

int SNet_isErrMarked(SNet *n)
{
  if (n->n_source)
    n = n->n_source;
  return (n->n_flags & NF_ERRREP) != 0;
}

void SPortList_init(SPortList *L)
{
  L->num = 0;
  L->port = 0;
}

void SPortList_add(SPortList *L,SPort *P)
{
  if ((L->num % PORTLIST_STEP) == 0) {
    SPort **pl = (SPort**) calloc(L->num+PORTLIST_STEP,sizeof(SPort*));
    if (L->port) {
      int i;

      for (i = 0;i < L->num;i++)
	pl[i] = L->port[i];
      free(L->port);
    }
    L->port = pl;
  }
  L->port[L->num++] = P;
}

/*
  Return the effective type for an INOUT port on a concat gate
 */
int SPort_effectiveTypeAux(SPort *P)
{
  switch (P->p_type->io) {
  case GIO_WIRE :
    return Concat_effectivePortType(P);
  case GIO_TRI :
    return GIO_OUT;
  }

  return P->p_type->io;
}

void SModule_check(SModule *M)
{
  HashElem *E;

  for (E = Hash_first(&M->m_gates);E;E = Hash_next(&M->m_gates,E)) {
    SGate *g = (SGate*) HashElem_obj(E);

    if (g->g_type && !SGate_isErrMarked(g))
      if ((g->g_type->gi_checkGate)(g))
	SGate_errMark(g);
  }

  for (E = Hash_first(&M->m_nets);E;E = Hash_next(&M->m_nets,E)) {
    SNet *n = (SNet*) HashElem_obj(E);
    int i,nout,ntri,nio,nw;

    if (n->n_merge || SNet_isErrMarked(n)) continue;

    SNet_errMark(n);
    nout = ntri = nio = nw = 0;
    for (i = 0;i < n->n_ports.num;i++) {
      SPort *P = n->n_ports.port[i];
      switch (P->p_type->io) {
      case GIO_OUT : nout++; break;
      case GIO_TRI : ntri++; break;
      case GIO_INOUT : nio++; break;
      case GIO_WIRE : nw++; break;
      }
    }


    if (nout + ntri + nio + nw == 0)
      errorNet(n->n_name,"No gates driving net '%s'.",n->n_name);
    else if (nout > 1)
      errorNet(n->n_name,"More than one non-tristate gate driving net '%s'.",n->n_name);
    else if (ntri != 0 && nout != 0)
      errorNet(n->n_name,"Net '%s' has mixed tristate and non-tristate drivers.",n->n_name);
  }
}

void SModule_init(SModule *M,EvQueue *Q)
{
  HashElem *E;

  for (E = Hash_first(&M->m_gates);E;E = Hash_next(&M->m_gates,E)) {
    SGate *g = (SGate*) HashElem_obj(E);

    if (g->g_type && g->g_type->gi_initGate)
      (g->g_type->gi_initGate)(Q,g);
  }
}

