/****************************************************************************
    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 ADD_Z	0
#define ADD_CO	1
#define ADD_A	2
#define ADD_B	3
#define ADD_CI	4

#define ADD_DELAY_ABZ	0
#define ADD_DELAY_ABCO	1
#define ADD_DELAY_CIZ	2
#define ADD_DELAY_CICO	3

static void Add_processEvent(SGate*,EvQueue*,SEvent*);
static int Add_checkGate(SGate*);

static SGateInfo add_info = {
  0,
  "add",0x1,
  5,{{"S",GIO_OUT,0},
       {"CO",GIO_OUT,0},
       {"A",GIO_IN,0},
       {"B",GIO_IN,0},
       {"CI",GIO_IN,0}},
  {{"A/B-S",bit(2)|bit(3),0},
   {"A/B-CO",bit(2)|bit(3),1},
   {"CI-S",bit(4),0},
   {"CI-CO",bit(4),1},
   {0}},

  Generic_copyGate,
  Add_processEvent,
  Add_checkGate,
  Nop_initGate,
  Generic_setProp,
  0,
  0,
  Generic_propFrwdDelay,
  Generic_propBackDelay,
  Generic_delay,
};

void init_add()
{
  SGateInfo_register(&add_info,0);
}

static int Add_checkGate(SGate *g)
{
  SPort *Z = g->g_ports.port[ADD_Z];
  SPort *CO = g->g_ports.port[ADD_CO];
  SPort *A = g->g_ports.port[ADD_A];
  SPort *B = g->g_ports.port[ADD_B];
  SPort *CI = g->g_ports.port[ADD_CI];
  int n;

  if (CI->p_net->n_nbits != 1 || CO->p_net->n_nbits != 1) {
    errorGate(g->g_name,"Carry inputs/outputs must be single bit.");
    return -1;
  }

  n = Z->p_net->n_nbits;
  if (A->p_net->n_nbits != n || B->p_net->n_nbits != n) {
    errorGate(g->g_name,"Inputs and output must be same bit width.");
    return -1;
  }

  return 0;
}

static void Add_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  SPort *Z = g->g_ports.port[ADD_Z];
  SPort *CO = g->g_ports.port[ADD_CO];

  SState *aS = SGate_allocPortState(g,ADD_A);
  SState *bS = SGate_allocPortState(g,ADD_B);
  SState *ciS = SGate_allocPortState(g,ADD_CI);
  SState *zS = alloc_SState();
  SState *coS = alloc_SState();
  int inUnknown = 0;
  int wc = (Z->p_state.nbits >> SSWORDSHIFT);		/* Index # of top word */
  int topnbits = Z->p_state.nbits & SSBITMASK;		/* Number of bits used in top word */
  unsigned mask;					/* Mask for top word */
  int carry_event;					/* Event was carry in change */
  int delay;						/* Delay value */
  int i;

  if (topnbits == 0) {
    wc--;
    topnbits = SSBITMASK+1;
    mask = ~0;
  } else
    mask = (1<<(topnbits))-1;

  SState_reinit(zS,Z->p_state.nbits);
  SState_reinit(coS,CO->p_state.nbits);

  carry_event = IsChangeOn(E,g,ADD_CI);

  /*
   * Check if there are any input bits other than 0 or 1 (unknown, floating, etc.)
   */
  if ((ciS->flt[0] & 0x1)) inUnknown = 1;
  for (i = 0;i <= wc && !inUnknown;i++) {
    if (i == wc) {
      if ((aS->flt[i] & mask)) inUnknown = 1;
      if ((bS->flt[i] & mask)) inUnknown = 1;
    } else {
      if (aS->flt[i]) inUnknown = 1;
      if (bS->flt[i]) inUnknown = 1;
    }
  }

  if (inUnknown) {
    /*
     * If there are any unknown input bits, output unknown.
     */
    SState_unknown(zS);
    SState_unknown(coS);
  } else {
    unsigned sum,carry;

    carry = ciS->one[0] & 0x1;				/* set carry in */
    for (i = 0;i <= wc;i++) {
      unsigned A = aS->one[i] & LMASK(aS->nbits&SSBITMASK);
      unsigned B = bS->one[i] & LMASK(bS->nbits&SSBITMASK);

      sum = A + B + carry;		/* new sum value */

      carry = ((B&0x1) + (A&0x1) + carry) >> 1;
      carry += (A>>1) + (B>>1);
      if (i != wc)
	carry >>= SSWORDSIZE-1;
      else
	carry >>= topnbits-1;

      zS->one[i] = sum;
      zS->zero[i] = ~sum;
      zS->flt[i] = 0;
    }
    coS->one[0] = carry;
    coS->zero[0] = ~carry;
    coS->flt[0] = 0;
  }

  if (carry_event)
    delay = g->g_delayParms[ADD_DELAY_CIZ];
  else
    delay = g->g_delayParms[ADD_DELAY_ABZ];

  EvQueue_setPort(Q,Z,zS,delay);

  if (carry_event)
    delay = g->g_delayParms[ADD_DELAY_CIZ];
  else
    delay = g->g_delayParms[ADD_DELAY_ABZ];

  EvQueue_setPort(Q,CO,coS,delay);

  free_SState(zS);
  free_SState(coS);
  free_SState(aS);
  free_SState(bS);
  free_SState(ciS);
}
