/****************************************************************************
    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 RAM_D	0	/* Data in/out */
#define RAM_A	1	/* Address */
#define RAM_CS	2	/* Chip select */
#define RAM_WE	3	/* Write enable */ 
#define RAM_OE	4	/* Output enable */

#define RSO_NOP		0	/* Do not modify memory */
#define RSO_XSTORE	1	/* Store unknown value at address */
#define RSO_DSTORE	2	/* Store data value at address */
#define RSO_WIPE	3	/* Invalidate all memory locs. */

#define ROO_DATA	0	/* Output addressed data */
#define ROO_ZDATA	1	/* Output float */
#define ROO_XDATA	2	/* Output unknown */

#define RAM_DELAY_OED		0	/* Delay from OE to output */
#define RAM_DELAY_CSD		1	/* Delay from CS to output */
#define RAM_DELAY_AD		2	/* Delay from address to output */
#define RAM_DELAY_ADDRSETUP	3	/* Setup time for address */
#define RAM_DELAY_DATASETUP	4	/* Setup time for data */
#define RAM_DELAY_ADDRHOLD	5	/* Hold time for address */
#define RAM_DELAY_DATAHOLD	6	/* Hold time for data */

#define max(a,b)	((a)>(b)?(a):(b))

#define sp(a,b)		(((a)<<8)|(b))

#define CODE(c)		((c) == SYM_ONE ? 1 : ((c) == SYM_ZERO ? 0 : -1)) 
#define CTLSYM(c)	(((c) == SYM_ONE || (c) == SYM_ZERO) ? (c) : SYM_UNKNOWN)


struct ram_data {
  int		abits;			/* # bits in address */
  int		dbits;			/* # bits in data */
  unsigned	addrMask;		/* Mask to select address bits */

  simTime	a_time;			/* Time of last addr change */ 
  simTime	d_time;			/* Time of last data change */ 
  simTime	oe_time;		/* Time of last data change */ 
  simTime	cs_time;		/* Time of last data change */ 
  simTime	we_time;		/* Time of last data change */ 
  simTime	store_time;		/* Time memory was written */

  int		is_stable;		/* Memory has stablized at least once */

  Memory	*mem;			/* Actual memory data */

  char		*memfile;		/* File to load on startup */
};

static void Ram_processEvent(SGate*,EvQueue*,SEvent*);
static int Ram_checkGate(SGate*);
static void Ram_initGate(EvQueue*,SGate*);
static void Ram_setProp(SGate*,const char*,const void*);
static void Ram_command(EvQueue*,SGate*,const char*);
static Memory *Ram_getMem(SGate*);

static SGateInfo ram_info = {
  GT_RAM,
  "ram",0x0,
  5,{{"D",GIO_INOUT,0},
     {"A",GIO_IN,0},
     {"CS",GIO_IN,0},
     {"WE",GIO_IN,0},
     {"OE",GIO_IN,0}},

  {{"OE-D",0,-1},
   {"CS-D",0,-1},
   {"A-D",0,-1},
   {"addr_setup",0,-1},
   {"data_setup",0,-1},
   {"addr_hold",0,-1},
   {"data_hold",0,-1},
   {0}},

  Generic_copyGate,
  Ram_processEvent,
  Ram_checkGate,
  Ram_initGate,
  Ram_setProp,
  Ram_command,
  Ram_getMem,
  Generic_propFrwdDelay,
  Generic_propBackDelay,
  Generic_delay,
};

struct ram_data *getRamData(SGate *g)
{
  if (!g->g_data) {
    struct ram_data *rd = (struct ram_data*) malloc(sizeof(struct ram_data));
    g->g_data = rd;

    rd->mem = 0;
    rd->memfile = 0;
    rd->a_time = 0;
    rd->oe_time = 0;
    rd->is_stable = 0;
  }

  return (struct ram_data *) g->g_data;
}


static void Ram_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  struct ram_data *rd = (struct ram_data*) g->g_data;
  SPort *D = g->g_ports.port[RAM_D];
  SState *A = SGate_allocPortState(g,RAM_A);
  SState *CS = SGate_allocPortState(g,RAM_CS);
  SState *OE = SGate_allocPortState(g,RAM_OE);
  SState *WE = SGate_allocPortState(g,RAM_WE);
  unsigned char *d,*u;
  int oop,wop;
  int mar_valid;	/* MAR data is valid */
  int mdr_valid;	/* MDR data is valid */
  int mar_setup;	/* Setup time has elapsed for MAR data */
  int mdr_setup;	/* Setup time has elapsed for MDR data */
  unsigned mar,mdr;	/* The MAR and MDR */
  int oe,cs,we;
  int i;
  SState *dout = alloc_SState();
  int d1,d2,d3;

  mar_valid = mdr_valid = 1;
  mar_setup = mdr_setup = 1;

  /*
   * Don't process memory events until inputs have stablized at least once.
   */
  if (!rd->is_stable) {
    if (SState_isLogic(A) && SState_isLogic(CS) && SState_isLogic(OE) && SState_isLogic(WE))
      rd->is_stable = 1;
    else {
      return;
    }
  } 

  SState_reinit(dout,rd->dbits);

  /*
    Process incomming events and record event times.
   */


  if (SState_convertToInt(&D->p_net->n_state,&mdr) < 0)
    mdr_valid = 0;

  if (SState_convertToInt(A,&mar) < 0)
    mar_valid = 0;

  if (E->evclass == EV_GATE) rd->a_time = Q->curStep;
  if (IsChangeOn(E,g,RAM_A)) rd->a_time = Q->curStep;
  if (IsChangeOn(E,g,RAM_D)) rd->d_time = Q->curStep;
  if (IsChangeOn(E,g,RAM_CS)) rd->cs_time = Q->curStep;
  if (IsChangeOn(E,g,RAM_OE)) rd->oe_time = Q->curStep;
  if (IsChangeOn(E,g,RAM_WE)) rd->we_time = Q->curStep;

  oe = SState_getBitSym(OE,0);
  oe = CTLSYM(oe);
  cs = SState_getBitSym(CS,0);
  cs = CTLSYM(cs);
  we = SState_getBitSym(WE,0);
  we = CTLSYM(we);

  if (rd->a_time + g->g_delayParms[RAM_DELAY_ADDRSETUP] > Q->curStep)
    mar_setup = 0;
  if (rd->d_time + g->g_delayParms[RAM_DELAY_DATASETUP] > Q->curStep)
    mdr_setup = 0;

  /*  printf("comment mem: oe=%d  cs=%d  we=%d\n",oe,cs,we); */

  /*
    Select the memory store mode
   */
  switch (sp(cs,we)) {
  case sp(SYM_ZERO,SYM_ZERO) :		/* Standard write operation */
    if (mar_valid && mar_setup) {	/* Address line is valid */
      if (mdr_valid)
	wop = RSO_DSTORE;		/* Data is valid */
      else
	wop = RSO_XSTORE;		/* Data is invalid */
    } else
      wop = RSO_WIPE;			/* Oops address line is unknown */
    break;
  case sp(SYM_ONE,SYM_ZERO) :
  case sp(SYM_ONE,SYM_UNKNOWN) :
  case sp(SYM_ONE,SYM_ONE) :
  case sp(SYM_ZERO,SYM_ONE) :
  case sp(SYM_UNKNOWN,SYM_ONE) :
    wop = RSO_NOP; 			/* No write operation */
    break;
  default :				/* Write line status unknown */
    if (mar_valid && mar_setup)
      wop = RSO_XSTORE;			/* If address was valid, store X */
    else
      wop = RSO_WIPE;			/* Oops address line is unknown */
  }

  /*
    Select the memory store output mode
   */
  switch (sp(cs,oe)) {
  case sp(SYM_ZERO,SYM_ZERO) :
    if (mar_valid)
      oop = ROO_DATA;
    else
      oop = ROO_XDATA;
    break;
  case sp(SYM_ONE,SYM_ZERO) :
  case sp(SYM_ONE,SYM_UNKNOWN) :
  case sp(SYM_ONE,SYM_ONE) :
  case sp(SYM_ZERO,SYM_ONE) :
  case sp(SYM_UNKNOWN,SYM_ONE) :
    oop = ROO_ZDATA; 
    break;
  default :
    oop = ROO_XDATA;
  }

  if (wop == RSO_WIPE) oop = ROO_XDATA;

  if (mar_valid)
    Memory_lookup(rd->mem,mar&rd->addrMask,&d,&u);

  switch (wop) {
  case RSO_XSTORE :
    /*  printf("comment mem: store unknown\n"); */
    *u = 1;
    break;
  case RSO_DSTORE :
    /* printf("comment mem: store data %x @ %x\n",mdr,mar); */
    for (i = 0;i < rd->mem->nbytes;i++)
      d[i] = 0xff & (mdr >> (i<<3));
    *u = 0;
    break;
  case RSO_WIPE :
    /* printf("comment mem: erase all\n"); */
#if 0
    Memory_flush(rd->mem);
#endif
    sendMsg("simerror Write to unknown address on memory '%s'.",g->g_name);
    Q->flags &= ~(EVF_RUN|EVF_PERSRUN);
    break;
  }

  switch (oop) {
  case ROO_DATA :
    if (*u) {
      SState_unknown(dout);
      /* printf("comment mem: output unknown @ %x\n",mar); */
    } else {
      mdr = 0;
      for (i = 0;i < rd->mem->nbytes;i++)
	mdr |= d[i] << (i<<3);
      SState_convertFromInt(dout,mdr);
      /* printf("comment mem: output %x @ %x\n",mdr,mar); */
    }
    break;
  case ROO_XDATA :
    /* printf("comment mem: output unknown\n"); */
    SState_unknown(dout);
    break;
  case ROO_ZDATA :
    /* printf("comment mem: output float\n"); */
    SState_float(dout);
    break;
  }

  d1 = rd->a_time + g->g_delayParms[RAM_DELAY_AD];
  d2 = rd->oe_time + g->g_delayParms[RAM_DELAY_OED];
  d3 = rd->cs_time + g->g_delayParms[RAM_DELAY_CSD];

  d1 = max(d1,d2);
  d1 = max(d1,d3);

  EvQueue_setPort(Q,D,dout,d1-Q->curStep);

  free_SState(dout);
  free_SState(A);
  free_SState(CS);
  free_SState(OE);
  free_SState(WE);
}

static int Ram_checkGate(SGate *g)
{
  SPort *A = g->g_ports.port[RAM_A];
  SPort *D = g->g_ports.port[RAM_D];
  SPort *CS = g->g_ports.port[RAM_CS];
  SPort *WE = g->g_ports.port[RAM_WE];
  SPort *OE = g->g_ports.port[RAM_OE];

  if (CS->p_state.nbits != 1 || WE->p_state.nbits != 1 || OE->p_state.nbits != 1) {
    errorGate(g->g_name,"The control pins CS, WE and OE must all be single-bit.");
    return -1;
  }

  if (A->p_state.nbits > 32) {
    errorGate(g->g_name,"Addresses greater than 32 bits are not supported.");
    return -1;
  }

  if (D->p_state.nbits > 32) {
    errorGate(g->g_name,"Data lines greater than 32 bits are not supported.");
    return -1;
  }

  return 0;
}

static void Ram_initGate(EvQueue *Q,SGate *g)
{
  struct ram_data *rd = getRamData(g);

  g->g_data = rd;

  rd->abits = g->g_ports.port[RAM_A]->p_net->n_nbits;
  rd->dbits = g->g_ports.port[RAM_D]->p_net->n_nbits;
  rd->addrMask = (rd->abits == SSWORDSIZE) ? SSWORDMASK : ((1<<rd->abits)-1);

  rd->a_time = 0;
  rd->d_time = 0;
  rd->oe_time = 0;
  rd->cs_time = 0;
  rd->we_time = 0;

  rd->mem = new_Memory(rd->abits,rd->dbits);

  if (rd->memfile) {
    int r = Memory_readFile(rd->mem,rd->memfile);
    if (r < 0) {
      error("Unable to open memory file '%s' for load or dump.",rd->memfile);
    } else if (r > 0) {
      error("Syntax error in memory file '%s'.",rd->memfile);
    }
  }
}

static void Ram_command(EvQueue *Q,SGate *g,const char *cmd)
{
  struct ram_data *rd = (struct ram_data*) g->g_data;
  char fileName[STRMAX];
  int r = 0;

  if (sscanf(cmd," load %[^\n]",fileName) == 1) {
    r = Memory_readFile(rd->mem,fileName);
    EvQueue_qGateEv(Q,g,0,0,0);
  } else if (sscanf(cmd," dump %[^\n]",fileName) == 1) {
    r = Memory_writeFile(rd->mem,fileName);
  } else
    error("Unknown command received by RAM element.");

  if (r < 0) {
    error("Unable to open memory file '%s' for load or dump.",fileName);
  } else if (r > 0) {
    error("Syntax error in memory file '%s'.",fileName);
  }
}

static void Ram_setProp(SGate *g,const char *prop,const void *value)
{
  struct ram_data *rd = getRamData(g);

  if (strcmp(prop,"/mem") == 0)
    rd->memfile = strdup((char*)value);
}

static Memory *Ram_getMem(SGate *g)
{
  struct ram_data *rd = getRamData(g);
  return rd->mem;
}

void init_ram()
{
  SGateInfo_register(&ram_info,0);
}
