/****************************************************************************
    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 FF_Q	0
#define FF_NQ	1
#define FF_D	2
#define FF_EN	3
#define FF_CLR	4
#define FF_CK	5

#define FF_DELAY_SETUP	0
#define FF_DELAY_HOLD	1
#define FF_DELAY_OUT	2

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

static void Flipflop_processEvent(SGate*,EvQueue*,SEvent*);
static int Flipflop_checkGate(SGate*);
static void Flipflop_initGate(EvQueue*,SGate*);

static SGateInfo ff_info = {
  0,
  "ff",0x0,
  6,{{"Q",GIO_OUT,PF_CUT},
     {"_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,
  Flipflop_processEvent,
  Flipflop_checkGate,
  Flipflop_initGate,
  Generic_setProp,
};

static void Flipflop_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  SPort *Z = g->g_ports.port[FF_Q];
  SPort *NZ = g->g_ports.port[FF_NQ];
  struct ff_data *rd = (struct ff_data*) g->g_data;
  SState *CLR = SGate_allocPortState(g,FF_CLR);
  int clr = SState_getBitSym(CLR,0);
  int hold_delay, out_delay, setup_delay;
  SState *S = alloc_SState();
  int do_set = 1;

  hold_delay = g->g_delayParms[FF_DELAY_HOLD];
  out_delay = g->g_delayParms[FF_DELAY_OUT];
  setup_delay = g->g_delayParms[FF_DELAY_SETUP];

  free_SState(CLR);

  if (IsChangeOn(E,g,FF_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);
    } else {
      rd->last_change = Q->curStep;			/* Mark time of last data change */
      do_set = 0;					/* Do not change output */
    }
  } 

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

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

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

    SState *CK = SGate_allocPortState(g,FF_CK);
    SState *EN = SGate_allocPortState(g,FF_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,FF_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;
    }

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

  if (do_set) {
    EvQueue_setPort(Q,Z,S,out_delay);
    SState_not(S,S);
    EvQueue_setPort(Q,NZ,S,out_delay);
  }

  free_SState(S);

}

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

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

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


void init_flipflop()
{
  SGateInfo_register(&ff_info,0);
}
