/****************************************************************************
    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 CLOCK_Z	0

struct clock_data {
  int	omega;		/* Clock cycle time */
  int	phi;		/* Phase (in %) */
  int	duty;		/* Duty cycle (in %) */

  int	start;		/* Start point in cycles */
  int	low;		/* Epochs in low state */
  int	high;		/* Epochs in high state */
};

static SGate *Clock_copyGate(SGate *sg,const char *name,SModule *M);
static void Clock_processEvent(SGate*,EvQueue*,SEvent*);
static void Clock_initGate(EvQueue*,SGate*);
static void Clock_setProp(SGate*,const char*,const void*);

static SGateInfo clock_info = {
  0,
  "clock",0x0,
  1,{{"Z",GIO_OUT,PF_CLOCK|PF_CUT}},

  {{0}},

  Clock_copyGate,
  Clock_processEvent,
  Nop_checkGate,
  Clock_initGate,
  Clock_setProp
};

void init_clock()
{
  SGateInfo_register(&clock_info,0);
}

static SGate *Clock_copyGate(SGate *sg,const char *name,SModule *M)
{
  SGate *ng = Generic_copyGate(sg,name,M);
  struct clock_data *nd = (struct clock_data *) malloc(sizeof(struct clock_data));
  struct clock_data *sd = (struct clock_data *) sg->g_data;

  ng->g_data = nd; 
  *nd = *sd;

  return ng;
}

static void Clock_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  SPort *Z = g->g_ports.port[CLOCK_Z];
  SState *S = &Z->p_state;
  struct clock_data *cd = (struct clock_data*)g->g_data;
  int delay;

  if ((S->one[0] & 0x1)) {
    delay = cd->low;
    SState_zero(S);

    if ((Q->flags & EVF_NEGCLOCK) && (!Q->triggerClock || Q->triggerClock == g)) {
      if (--Q->clockCount <= 0) {
	Q->flags &= ~(EVF_POSCLOCK|EVF_NEGCLOCK);
	EvQueue_control(Q,EVC_STOP,0,Q->clockHold);
      }
    }

  } else {
    delay = cd->high;
    SState_one(S);
    S->one[0] &= LMASK(S->nbits&SSBITMASK);

    if ((Q->flags & EVF_POSCLOCK) && (!Q->triggerClock || Q->triggerClock == g)) {
      if (--Q->clockCount <= 0) {
	Q->flags &= ~(EVF_POSCLOCK|EVF_NEGCLOCK);
	EvQueue_control(Q,EVC_STOP,0,Q->clockHold);
      }
    }
  }

  /* Needed here, because of automatic compliment in setPort function */
  if (Z->p_comp)
    SState_not(S,S);

  EvQueue_setPort(Q,Z,S,0);
  EvQueue_qGateEv(Q,g,0,0,delay);
}

static void Clock_initGate(EvQueue *Q,SGate *g)
{
  SPort *Z = g->g_ports.port[CLOCK_Z];
  SState *S = alloc_SState();
  struct clock_data *cd = (struct clock_data*)g->g_data;

  Q->flags |= EVF_HASCLOCK;

  SState_zero(S);

  if (cd->start < cd->low) {
    EvQueue_setPort(Q,Z,S,0);
    EvQueue_qGateEv(Q,g,0,0,cd->low-cd->start);
  } else {
    SState_not(S,S);
    EvQueue_setPort(Q,Z,S,0);
    EvQueue_qGateEv(Q,g,0,0,cd->high-(cd->start-cd->low));
  }

  free_SState(S);
}

static void Clock_setProp(SGate *g,const char *prop,const void *value)
{
  int n = *((int*)value);
  struct clock_data *cd = (struct clock_data*)g->g_data;

  if (!cd) {
    cd = (struct clock_data*)malloc(sizeof(struct clock_data));
    g->g_data = cd;
  }

  if (strcmp(prop,"/omega") == 0)
    cd->omega = n;
  else if (strcmp(prop,"/phi") == 0)
    cd->phi = n;
  else if (strcmp(prop,"/duty") == 0)
    cd->duty = n;

  if (cd->phi < 0) cd->phi = 0;
  if (cd->phi > 99) cd->phi = 99;

  if (cd->duty < 1) cd->duty = 1;
  if (cd->duty > 99) cd->duty = 99;

  cd->low = (cd->omega*cd->duty)/100;
  if (cd->low < 1) 
    cd->low = 1;
  else if (cd->low >= cd->omega) 
    cd->low = cd->omega-1;

  cd->high = cd->omega - cd->low;

  cd->start = (cd->omega*cd->phi)/100;
  if (cd->start < 0) 
    cd->start = 0;
  else if (cd->start >= cd->omega) 
    cd->start = cd->omega-1;
}
