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

#define CONCAT_Z	0
#define CONCAT_I	1

#define DIR_UNKNOWN	0
#define DIR_BOGUS	-1
#define DIR_IZ		1
#define DIR_ZI		2

static void Concat_processEvent(SGate*,EvQueue*,SEvent*);
static int Concat_checkGate(SGate*);

static void Concat_propFrwdDelay(SPort *P,simTime t);
static void Concat_propBackDelay(SPort *P,simTime t);
static simTime Concat_delay(SPort *Pfrom,SPort *Pto);

static int count_outs(SPort *xP,int *nout);

typedef struct concat_data {
  int	dir;		/* Direction of merge */
  int	loopCheck;	/* Checking for loop */
  SPort	*blockP;	/* Blocked port */
} ConcatData;

static SGateInfo concat_info = {
  GT_CONCAT,
  "concat",0x0,
  2,{{"Z",GIO_WIRE,0},
     {"I",GIO_WIRE,PF_MULTI}},
  {{0}},

  Generic_copyGate,
  Concat_processEvent,
  Concat_checkGate,
  Nop_initGate,
  0,
  0,
  0,
  Concat_propFrwdDelay,
  Concat_propBackDelay,
  Concat_delay,
};

ConcatData *Concat_getData(SGate *g)
{
  if (!g->g_data) {
    ConcatData *cd = (ConcatData *) malloc(sizeof(ConcatData));
    g->g_data = cd;
    cd->dir = DIR_UNKNOWN;
    cd->loopCheck = 0;
    cd->blockP = 0;
  }
  return g->g_data;
}

int Concat_effectivePortType(SPort *P)
{
  SGate *g = P->p_gate;
  ConcatData *cd = (ConcatData *) Concat_getData(g);

  switch (cd->dir) {
  case DIR_IZ :
    if (P == g->g_ports.port[CONCAT_Z])
      return GIO_OUT;
    else
      return GIO_IN;
    break;
  case DIR_ZI :
    if (P == g->g_ports.port[CONCAT_Z])
      return GIO_IN;
    else
      return GIO_OUT;
    break;
  default :
    break;
  }

  return GIO_IN;
}

static void Concat_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  int i,b;
  ConcatData *cd = (ConcatData *) Concat_getData(g);


  switch (cd->dir) {
  case DIR_ZI :				/* Signals flow from Z to I */
    {
      SState *Z,*C;

      if (!IsChangeOn(E,g,CONCAT_Z)) break;

      Z = SGate_allocPortState(g,CONCAT_Z); 
      C = alloc_SState();

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

	SState_reinit(C,I->p_state.nbits);
	SState_copyRange(C,0,Z,I->p_state.nbits-1+b,b);
	b += I->p_state.nbits;

	EvQueue_setPort(Q,I,C,0);
      }

      free_SState(Z);
      free_SState(C);
    }
    break;
  case DIR_IZ :				/* Signals flow from I to Z */
    {
      SPort *Z = g->g_ports.port[CONCAT_Z];
      SState *C;

      if (IsChangeOn(E,g,CONCAT_Z)) break;

      C = alloc_SState();
      SState_reinit(C,Z->p_state.nbits);

      b = 0;
      for (i = CONCAT_I;i < g->g_ports.num;i++) {
	SState *I = SGate_allocPortState(g,i);

	SState_copyRange(C,b,I,I->nbits-1,0);

	b += I->nbits;
	free_SState(I);
      }

      EvQueue_setPort(Q,Z,C,0);

      free_SState(C);
    }
    break;
  default :
    error("Event received by uninstantiated concat %s.",g->g_name);
    break;
  }
}

/*
 * Set directions of all concat elements in the circuit.
 */
void Concat_setDirections(SModule *M)
{
  HashElem *E;
  NHash C;
  SGate **A;
  int num_left;
  int i,j;

  NHash_init(&C);

  /*
   * Build table of all concat elements. 
   */
  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->gi_code == GT_CONCAT)
      NHash_insert(&C,(nkey_t)g,g);
  }

  /*
   * Copy elements found to an array.
   */
  num_left = Hash_numElems(&C);
  A = (SGate**)malloc(sizeof(SGate*)*num_left);
  for (i = 0, E = Hash_first(&C);E;i++, E = Hash_next(&C,E))
    A[i] = (SGate*)HashElem_obj(E);
  NHash_uninit(&C);


  /*
   * Set directions of concat elements until we go through a whole pass
   * without any modifications.
   */
  for (;;) {
    int num_elim = 0;	/* Number of unknown concats eliminated */

    for (i = 0;i < num_left;i++) {
      SGate *g = A[i];
      ConcatData *cd = Concat_getData(g);
      SPort *P;
      int i_nout = 0,z_nout = 0;

      /*
       * Direction is already set for this concat
       */
      if (cd->dir != DIR_UNKNOWN) {
	A[i] = A[--num_left];
	num_elim++;
	continue;
      }

      /*
       * Scan I ports
       */
      for (j = CONCAT_I;j < g->g_ports.num;j++) {
	SPort *P = g->g_ports.port[j];
	count_outs(P,&i_nout);
      }

      P = g->g_ports.port[CONCAT_Z];
      count_outs(P,&z_nout);

      /*
       * Driving ports found on both sides
       */
      if (i_nout > 0 && z_nout > 0) {
	A[i] = A[--num_left];
	num_elim++;
	errorGate(g->g_name,"Inconsistent direction on concat element.");
	continue;
      }

      if (i_nout > 0) {
	cd->dir = DIR_IZ;
	A[i] = A[--num_left];
	num_elim++;
	continue;
      }

      if (z_nout > 0) {
	cd->dir = DIR_ZI;
	A[i] = A[--num_left];
	num_elim++;
	continue;
      }
    }
    if (num_elim == 0) break;
  }

  free(A);
}

/*
 * Count the number of "output" ports connected to the same net as xP.  xP
 * is excluded from the count.  The number of outputs found is added to *nout.
 */
static int count_outs(SPort *xP,int *nout)
{
  SNet *net = xP->p_net;
  int i;

  for (i = 0;i < net->n_ports.num;i++) {
    SPort *P = net->n_ports.port[i];
    if (P == xP) continue;

    switch (P->p_type->io) {
    case GIO_IN :
      break;
    case GIO_OUT :
    case GIO_TRI :
    case GIO_INOUT :
      (*nout)++;
      break;
    case GIO_WIRE :
      {
	SGate *g = P->p_gate;
	ConcatData *cd;

	if (g->g_type != &concat_info) {
	  errorGate(P->p_gate->g_name,"Bogus gate type %s when expecting concat.",g->g_type->gi_name);
	  break;
	}

	cd = (ConcatData *) Concat_getData(g);
	if (cd->blockP == P)
	  return 0;

	cd = (ConcatData *) Concat_getData(g);
	if (cd->dir > 0) {
	  if (cd->dir == DIR_IZ && P == g->g_ports.port[CONCAT_Z])
	    (*nout)++;
	  else if (cd->dir == DIR_ZI && P != g->g_ports.port[CONCAT_Z])
	    (*nout)++;
     
	}
      }
      break;
    default :
      errorGate(P->p_gate->g_name,"Bogus wire type %d found while checking concat ports.",P->p_type->io);
      break;
    }
  }
  return  0;
}

static int Concat_checkGate(SGate *g)
{
  ConcatData *cd;
  int n = 0;
  int i;

  for (i = CONCAT_I;i < g->g_ports.num;i++)
    n += g->g_ports.port[i]->p_net->n_nbits;

  if (n != g->g_ports.port[CONCAT_Z]->p_net->n_nbits) {
    errorGate(g->g_name,"Bit width mismatch (%d != %d).",n,g->g_ports.port[CONCAT_Z]->p_net->n_nbits);
    return -1;
  }

  cd = (ConcatData *) Concat_getData(g);

  if (cd->dir <= 0) {
    errorGate(g->g_name,"concat direction could not be determined.");
    return -1;
  }

  return 0;
}

static void Concat_propFrwdDelay(SPort *P,simTime t)
{
  SGate *g = P->p_gate;
  SPadInfo *pi = P->p_type;
  int i;

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

  if (pi->idx == 0) {
    for (i = 1;i < g->g_ports.num;i++)
      SNet_propFrwdDelay(g->g_ports.port[i]->p_net,t);
  } else {
    SNet_propFrwdDelay(g->g_ports.port[0]->p_net,t);
  }
}

static void Concat_propBackDelay(SPort *P,simTime t)
{
  SGate *g = P->p_gate;
  SPadInfo *pi = P->p_type;
  int i;

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

  if (pi->idx == 0) {
    for (i = 1;i < g->g_ports.num;i++)
      SNet_propBackDelay(g->g_ports.port[i]->p_net,t);
  } else {
    SNet_propBackDelay(g->g_ports.port[0]->p_net,t);
  }
}

static simTime Concat_delay(SPort *Pfrom,SPort *Pto)
{
  if (!Pto) return 0;

  if ((Pto->p_type->idx == 0 && Pfrom->p_type->idx == 0) ||
      (Pto->p_type->idx != 0 && Pfrom->p_type->idx != 0))
    return NO_TIME;

  return 0;
}

void init_concat()
{
  SGateInfo_register(&concat_info,0);
}

