/****************************************************************************
    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 REG_Q	0
#define REG_D	1
#define REG_EN	2
#define REG_CLR	3
#define REG_CK	4

#define REG_DELAY_SETUP	0
#define REG_DELAY_HOLD	1
#define REG_DELAY_OUT	2

struct reg_data {
  int		last_change;	/* Time of last data change */ 
  int		hold_wait;	/* Hold wait until this time */
  SState	state;		/* Register state */
};

static void Register_processEvent(SGate*,EvQueue*,SEvent*);
static int Register_checkGate(SGate*);
static void Register_initGate(EvQueue*,SGate*);
static simTime Register_delay(SPort *Pfrom,SPort *Pto);

static SGateInfo reg_info = {
  0,
  "register",0x0,
  5,{{"Q",GIO_OUT,PF_CUT},
     {"D",GIO_IN,PF_CUT},
     {"EN",GIO_IN,PF_CUT},
     {"CLR",GIO_IN,PF_CUT},
     {"CK",GIO_IN,PF_CLOCK|PF_CUT}},

  {{"setup",bit(1),-1},
   {"hold",0,-1},
   {"CK-Q",0,1},
   {0}},

  Generic_copyGate,
  Register_processEvent,
  Register_checkGate,
  Register_initGate,
  Generic_setProp,
  0,
  0,
  0,
  0,
  Register_delay
};

#if 0
static void Register_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  SPort *Z = g->g_ports.port[REG_Q];
  struct reg_data *rd = (struct reg_data*) g->g_data;
  SState *CLR = SGate_allocPortState(g,REG_CLR);
  int clr = SState_getBitSym(CLR,0);
  int hold_delay, out_delay, setup_delay;

  hold_delay = g->g_delayParms[REG_DELAY_HOLD];
  out_delay = g->g_delayParms[REG_DELAY_OUT];
  setup_delay = g->g_delayParms[REG_DELAY_SETUP];

  free_SState(CLR);

  if (IsChangeOn(E,g,REG_D)) {				/* Data in changed */
    if (Q->curStep < rd->hold_wait) {			/* Data changed to soon after clock */
      SState_unknown(&rd->state);
      EvQueue_setPort(Q,Z,&rd->state,out_delay);
    } else {
      rd->last_change = Q->curStep;			/* Mark time of last data change */
    }
  } 

  /*
   * If clear line is set, set register to zero.
   */
  if (clr == SYM_ZERO) {
    SState_zero(&rd->state);
    EvQueue_setPort(Q,Z,&rd->state,out_delay);
    return;
  }

  /*
   * If clear line is unknown, set register to unknown.
   */
  if (clr != SYM_ONE) {
    SState_unknown(&rd->state);
    EvQueue_setPort(Q,Z,&rd->state,out_delay);
    return;
  }

  /*
   * Check for clock transition.
   */
  if (IsChangeOn(E,g,REG_CK)) {
    SState *CK = SGate_allocPortState(g,REG_CK);
    SState *EN = SGate_allocPortState(g,REG_EN);
    int en = SState_getBitSym(EN,0);
    int ck = SState_getBitSym(CK,0);

    free_SState(EN);
    free_SState(CK);

    switch (en) { 
    case SYM_ZERO :	/* Clock is enabled */
      switch (ck) {
      case SYM_ONE :	/* Positive clock edge */
	if (Q->curStep < rd->last_change+setup_delay) {
	  SState_unknown(&rd->state);
	} else {
	  SState *D = SGate_allocPortState(g,REG_D);
	  SState_copy(&rd->state,D);
	  rd->hold_wait = Q->curStep + hold_delay;
	  free_SState(D);
	}
	break;
      case SYM_ZERO :	/* Negative clock edge */	
	break;
      default :		/* Bogus clock value */
	SState_unknown(&rd->state);
	break;
      }
      break;
    case SYM_ONE :	/* Clock is disabled */
      break;
    default :
      SState_unknown(&rd->state);
      break;
    }

    EvQueue_setPort(Q,Z,&rd->state,out_delay);
  }
}
#endif

#if 1
static void Register_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  SPort *Z = g->g_ports.port[REG_Q];
  struct reg_data *rd = (struct reg_data*) g->g_data;
  SState *CLR = SGate_allocPortState(g,REG_CLR);
  int clr = SState_getBitSym(CLR,0);
  int hold_delay, out_delay, setup_delay;
  SState *S = alloc_SState();
  int do_set = 0;
 
  hold_delay = g->g_delayParms[REG_DELAY_HOLD];
  out_delay = g->g_delayParms[REG_DELAY_OUT];
  setup_delay = g->g_delayParms[REG_DELAY_SETUP];

  free_SState(CLR);

  if (IsChangeOn(E,g,REG_D)) {				/* Data in changed */
    if (Q->curStep < rd->hold_wait) {			/* Data changed to soon after clock */
      SState_unknown(&rd->state);
      SState_copy(S,&rd->state);
      do_set = 1;
    } else {
      rd->last_change = Q->curStep;			/* Mark time of last data change */
    }
  } 

  if (clr == SYM_ZERO) {
    /*
     * If clear line is set, set register to zero.
     */

    SState_zero(&rd->state);
    SState_copy(S,&rd->state);
    do_set = 1;
  } else if (clr != SYM_ONE) {
    /*
     * If clear line is unknown, set register to unknown.
     */

    SState_unknown(&rd->state);
    SState_copy(S,&rd->state);
    do_set = 1;
  } else if (IsChangeOn(E,g,REG_CK)) {
    /*
     * Check for clock transition.
     */

    SState *CK = SGate_allocPortState(g,REG_CK);
    SState *EN = SGate_allocPortState(g,REG_EN);
    int en = SState_getBitSym(EN,0);
    int ck = SState_getBitSym(CK,0);

    free_SState(EN);
    free_SState(CK);

    switch (en) { 
    case SYM_ZERO :	/* Clock is enabled */
      switch (ck) {
      case SYM_ONE :	/* Positive clock edge */
	if (Q->curStep < rd->last_change+setup_delay) {
	  SState_unknown(&rd->state);
	} else {
	  SState *D = SGate_allocPortState(g,REG_D);
	  SState_copy(&rd->state,D);
	  rd->hold_wait = Q->curStep + hold_delay;
	  free_SState(D);
	}
	do_set = 1;
	break;
      case SYM_ZERO :	/* Negative clock edge */	
	break;
      default :		/* Bogus clock value */
	SState_unknown(&rd->state);
	do_set = 1;
	break;
      }
      break;
    case SYM_ONE :	/* Clock is disabled */
      break;
    default :
      SState_unknown(&rd->state);
      do_set = 1;
      break;
    }

    SState_copy(S,&rd->state);
  } else
    do_set = 0; 

  if (do_set) {
    EvQueue_setPort(Q,Z,S,out_delay);
  }
  free_SState(S);
}
#endif

static simTime Register_delay(SPort *Pfrom,SPort *Pto)
{
  SGate *g = Pfrom->p_gate;

  if (Pto) return 0;

  switch (Pfrom->p_type->idx) {
  case REG_Q :
    return g->g_delayParms[REG_DELAY_OUT];
  case REG_D :
  case REG_EN :
  case REG_CLR :
    return g->g_delayParms[REG_DELAY_SETUP];
  case REG_CK :
    return 0;
  default:
    return 0;
  }
}

static int Register_checkGate(SGate *g)
{
  return 0;
}

static void Register_initGate(EvQueue *Q,SGate *g)
{
  struct reg_data *rd = (struct reg_data*) malloc(sizeof(struct reg_data));

  g->g_data = rd;
  rd->last_change = 0;
  rd->hold_wait = 0;
  SState_init(&rd->state,g->g_ports.port[REG_Q]->p_net->n_nbits);
  SState_unknown(&rd->state);
}


void init_register()
{
  SGateInfo_register(&reg_info,0);
}
