/****************************************************************************
    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 ROM_D	0	/* Data in/out */
#define ROM_A	1	/* Address */
#define ROM_OE	2	/* Output enable */

#define ROM_DELAY_OED		0	/* Delay from OE to output */
#define ROM_DELAY_AD		1	/* Delay from address to output */

#define CTLSYM(c)	(((c) == SYM_ONE || (c) == SYM_ZERO) ? (c) : SYM_UNKNOWN)

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

struct rom_data {
  int 		abits;
  int 		dbits;
  unsigned	addrMask;
  simTime	a_time;
  simTime	oe_time;

  Memory	*mem;			/* Actual memory data */

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

static void Rom_processEvent(SGate*,EvQueue*,SEvent*);
static int Rom_checkGate(SGate*);
static void Rom_initGate(EvQueue*,SGate*);
static void Rom_setProp(SGate*,const char*,const void*);
static void Rom_command(EvQueue*,SGate *g,const char *cmd);
static Memory *Rom_getMem(SGate *);

static SGateInfo rom_info = {
  GT_ROM,
  "rom",0x0,
  3,{{"D",GIO_OUT,0},
     {"A",GIO_IN,0},
     {"OE",GIO_IN,0}},

  {{"OE-D",bit(2),0},
   {"A-D",bit(1),0},
   {0}},

  Generic_copyGate,
  Rom_processEvent,
  Rom_checkGate,
  Rom_initGate,
  Rom_setProp,
  Rom_command,
  Rom_getMem,
  Generic_propFrwdDelay,
  Generic_propBackDelay,
  Generic_delay,
};

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

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

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

static void Rom_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  struct rom_data *rd = (struct rom_data*) g->g_data;
  SPort *D = g->g_ports.port[ROM_D];
  SState *A = SGate_allocPortState(g,ROM_A);
  SState *OE = SGate_allocPortState(g,ROM_OE);
  SState *dout = alloc_SState();
  unsigned char *d,*u;
  int mar_valid;	/* MAR data is valid */
  unsigned mar;		/* The MAR */
  int oe,i;
  int d1,d2;

  mar_valid = 1;

  SState_reinit(dout,rd->dbits);

  /*
    Process incomming events and record event times.
   */
  if (SState_convertToInt(A,&mar) < 0)
    mar_valid = 0;

  if (E->evclass == EV_GATE) rd->a_time = Q->curStep;
  if (IsChangeOn(E,g,ROM_A)) rd->a_time = Q->curStep;
  if (IsChangeOn(E,g,ROM_OE)) rd->oe_time = Q->curStep;

  oe = SState_getBitSym(OE,0);
  oe = CTLSYM(oe);

  if (mar_valid && oe == SYM_ZERO) {
    int mdr;

    Memory_lookup(rd->mem,mar&rd->addrMask,&d,&u);
    mdr = 0;
    for (i = 0;i < rd->mem->nbytes;i++)
      mdr |= d[i] << (i<<3);

    if (*u)
      SState_unknown(dout);
    else
      SState_convertFromInt(dout,mdr);
  } else if (oe == SYM_ONE)
    SState_float(dout);
  else
    SState_unknown(dout);

  d1 = rd->a_time + g->g_delayParms[ROM_DELAY_AD];
  d2 = rd->oe_time + g->g_delayParms[ROM_DELAY_OED];

  d1 = max(d1,d2);

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

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

static int Rom_checkGate(SGate *g)
{
  SPort *A = g->g_ports.port[ROM_A];
  SPort *D = g->g_ports.port[ROM_D];
  SPort *OE = g->g_ports.port[ROM_OE];

  if (OE->p_state.nbits != 1) {
    errorGate(g->g_name,"The control pin OE must 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 Rom_initGate(EvQueue *Q,SGate *g)
{
  struct rom_data *rd = getRomData(g);

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

  rd->a_time = 0;
  rd->oe_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 Rom_command(EvQueue *Q,SGate *g,const char *cmd)
{
  struct rom_data *rd = (struct rom_data*) g->g_data;
  char fileName[STRMAX];
  int r = 0;

  /* printf("comment Rom_command\n"); */

  if (sscanf(cmd," load %[^\n]",fileName) == 1) {
    r = Memory_readFile(rd->mem,fileName);
  } else if (sscanf(cmd," dump %[^\n]",fileName) == 1) {
    r = Memory_writeFile(rd->mem,fileName);
  } else
    error("Unknown command received by ROM 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 Rom_setProp(SGate *g,const char *prop,const void *value)
{
  struct rom_data *rd = getRomData(g);

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

static Memory *Rom_getMem(SGate *g)
{
  struct rom_data *rd = getRomData(g);
  return rd->mem;
}

void init_rom()
{
  SGateInfo_register(&rom_info,0);
}
