/****************************************************************************
 * This file creates the ALU-tables needed by MyCPU.
 * Author: Dennis Kuschel, K.-G.-Kiesinger-Allee 14, 28279 Bremen, Germany
 ****************************************************************************/

/* NOTE: This file was tested with the following compilers:
 *
 * Microsoft Visual C, Turbo C, GNU C
 *
 * To compile under Linux type  # gcc -Wall -D_LINUX -o alugen.out alugen.c
 */

#include <stdio.h>
#ifndef _LINUX
#include <conio.h>
#endif


/* #define SPLITFLAGFILE  - define this to create ALU version 1 table files */

/* file names */
#define FNAME_ALU1D     "alu1data.bin"
#define FNAME_ALU1D64   "alu1data-64.bin"
#define FNAME_ALU1D256  "alu1data-256.bin"
#define FNAME_ALU2D     "alu2data.bin"
#define FNAME_ALUF      "aluflags.bin"

/* FLAGS */
#define FLAG_C       0x01  // D0
#define FLAG_Z       0x02  // D1
#define FLAG_V       0x04  // D2

/* ALU 2Byte - Modes */    // A17,A18,A19 + /OE,  CARRY = A16
#define MODE2_AND    0x00
#define MODE2_ORA    0x01
#define MODE2_EOR    0x02
#define MODE2_ADC    0x03
#define MODE2_SBC    0x04
#define MODE2_MULL   0x05
#define MODE2_MULH   0x06
#define MODE2_DIV    0x07

/* ALU 1Byte - Modes */    // A8,A9,A10 + /OE,  CARRY = A11
#define MODE1_CINC   0x08
#define MODE1_CDEC   0x09
#define MODE1_LSL    0x0A
#define MODE1_LSR    0x0B
#define MODE1_ROL    0x0C
#define MODE1_ROR    0x0D
#define MODE1_INC    0x0E
#define MODE1_DEC    0x0F


/* increment and set carry on overflow */
void ALU1_INC(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = din+1;
  *pC = !*pdout;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

/* decrement and clear carry on overflow */
void ALU1_DEC(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = din-1;
  *pC = *pdout != 0xFF;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

/* inc when carry is set and set carry on overflow */
void ALU1_CINC(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{   
  if (*pC)
  {
    *pdout = din+1;
    *pC = !*pdout;
  }
  else
  {
    *pdout = din;
    *pC = 0;
  }
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

/* dec when carry is not set and clear carry on overflow */
void ALU1_CDEC(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{   
  if (*pC)
  {
    *pdout = din;
    *pC = 1;
  }
  else
  {
    *pdout = din-1;
    *pC = *pdout != 0xFF;
  }
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU1_LSL(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = (din << 1) & ~1;
  *pC = (din & 0x80)!=0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU1_LSR(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = (din >> 1) & ~0x80;
  *pC = (din & 1)!=0;
  *pZ = !*pdout;
  *pV = 0;
}

void ALU1_ROL(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = (din << 1) & ~1;
  if (*pC) *pdout |= 1;
  *pC = (din & 0x80)!=0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU1_ROR(unsigned char din, unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = (din >> 1) & ~0x80;
  if (*pC) *pdout |= 0x80;
  *pC = (din & 1)!=0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_AND(unsigned char din1, unsigned char din2,
              unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = din1 & din2;
  *pC = 0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_OR(unsigned char din1, unsigned char din2,
             unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = din1 | din2;
  *pC = 0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_EOR(unsigned char din1, unsigned char din2,
              unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  *pdout = din1 ^ din2;
  *pC = 0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_ADC(unsigned char din1, unsigned char din2,
              unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  unsigned short erg;
  erg = din1 + din2;
  if (*pC) erg++;
  *pdout = (unsigned char)erg;
  *pC = (erg>0xFF);
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_SBC(unsigned char din1, unsigned char din2,
              unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  unsigned short erg;
  erg = 0x100 + din1 - din2;
  if (!*pC) erg--;
  *pdout = (unsigned char)erg;
  *pC = (erg>0xFF);
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_MULL(unsigned char din1, unsigned char din2,
               unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  unsigned short erg;
  erg = din1 * din2;
  *pdout = (unsigned char)erg;
  *pC = 1;    // Carry wird auf 1 gesetzt!  (vereinfacht den Mikrocode)
              // ein overflow kann durch MULH ermittelt werden
  *pZ = !erg;          //das Zero-Flag bezieht sich auf das 16Bit-Ergebnis
  *pV = (*pdout & 0x80)!=0;
}

void ALU2_MULH(unsigned char din1, unsigned char din2,
               unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  unsigned short erg;
  erg = din1 * din2;
  *pdout = (unsigned char)(erg>>8);
  *pC = *pdout != 0;     //C=1 wenn das Ergebnis größer 8 bit ist
                         //MULH kann zum ermitteln der AKKU-Flags benutzt werden. Dazu ist der 
                         //AKKU-Wert in das erste Register (REG1) der ALU zu schreiben.
  *pZ = !din1;               //Z = 1 wenn REG1 == 0
  *pV = (din1 & 0x80)!=0;    //V = 1 wenn Bit7 von REG1 == 1
}

void ALU2_DIV(unsigned char din1, unsigned char din2,
              unsigned char *pdout, int *pC, int *pZ, int *pV)
{
  if (din2)
    *pdout = din1 / din2;
  else
    *pdout = 0xFF;
  *pC = 0;
  *pZ = !*pdout;
  *pV = (*pdout & 0x80)!=0;
}



static int generateALUtables(void)
{
  FILE *fAludata1, *fAludata2, *fAluflags;
  unsigned long ulAdr;
  unsigned char flags;
  unsigned char adrl, adrh;
  unsigned char d1, d2, fl;
  int mode, c, z, v;
  int status = 0;

  fAludata1 = fopen(FNAME_ALU1D,"wb");
  if (fAludata1 == NULL)
  {
    printf("failed to generate file\n");
    return -1;
  }
  fAludata2 = fopen(FNAME_ALU2D,"wb");
  if (fAludata2 == NULL)
  {
    fclose(fAludata1);
    printf("failed to generate file\n");
    return -1;
  }
  fAluflags = fopen(FNAME_ALUF,"wb");
  if (fAluflags == NULL)
  {
    fclose(fAludata1);
    fclose(fAludata2);
    printf("failed to generate file\n");
    return -1;
  }


  for (ulAdr = 0; ulAdr < 0x100000; ulAdr++)
  {

    /* generate data + flag table for 2 byte ALU operations */

    adrl = (unsigned char)(ulAdr & 0xFF);
    adrh = (unsigned char)((ulAdr>>8) & 0xFF);
    mode = (int)((ulAdr >> 17) & 7);
    c    = (int)((ulAdr >> 16) & 1);

    switch (mode)
    {
      case MODE2_AND : ALU2_AND (adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_ORA : ALU2_OR  (adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_EOR : ALU2_EOR (adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_ADC : ALU2_ADC (adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_SBC : ALU2_SBC (adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_MULL: ALU2_MULL(adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_MULH: ALU2_MULH(adrl, adrh, &d2, &c, &z, &v); break;
      case MODE2_DIV : ALU2_DIV (adrl, adrh, &d2, &c, &z, &v); break;
      default: break;
    };
    if (c) flags = FLAG_C; else flags = 0;
    if (z) flags |= FLAG_Z;
    if (v) flags |= FLAG_V;
    fl = flags;


    /* generate flag table for 1 byte ALU operations */

    c = (int)((ulAdr >> 16) & 1);

    switch (mode+8)
    {
      case MODE1_CINC: ALU1_CINC(adrl, &d1, &c, &z, &v); break;
      case MODE1_CDEC: ALU1_CDEC(adrl, &d1, &c, &z, &v); break;
      case MODE1_LSL : ALU1_LSL (adrl, &d1, &c, &z, &v); break;
      case MODE1_LSR : ALU1_LSR (adrl, &d1, &c, &z, &v); break;
      case MODE1_ROL : ALU1_ROL (adrl, &d1, &c, &z, &v); break;
      case MODE1_ROR : ALU1_ROR (adrl, &d1, &c, &z, &v); break;
      case MODE1_INC : ALU1_INC (adrl, &d1, &c, &z, &v); break;
      case MODE1_DEC : ALU1_DEC (adrl, &d1, &c, &z, &v); break;
      default: break;
    };
    if (c) flags = FLAG_C; else flags = 0;
    if (z) flags |= FLAG_Z;
    if (v) flags |= FLAG_V;
    fl |= flags << 4;


    if (ulAdr < 0x1000)
    {
      /* generate data table for 1 byte ALU operations */

      adrl = (unsigned char)(ulAdr & 0xFF);
      mode = (int)((ulAdr >> 9) & 7);
      c    = (int)((ulAdr >> 8) & 1);

      switch (mode+8)
      {
        case MODE1_CINC: ALU1_CINC(adrl, &d1, &c, &z, &v); break;
        case MODE1_CDEC: ALU1_CDEC(adrl, &d1, &c, &z, &v); break;
        case MODE1_LSL : ALU1_LSL (adrl, &d1, &c, &z, &v); break;
        case MODE1_LSR : ALU1_LSR (adrl, &d1, &c, &z, &v); break;
        case MODE1_ROL : ALU1_ROL (adrl, &d1, &c, &z, &v); break;
        case MODE1_ROR : ALU1_ROR (adrl, &d1, &c, &z, &v); break;
        case MODE1_INC : ALU1_INC (adrl, &d1, &c, &z, &v); break;
        case MODE1_DEC : ALU1_DEC (adrl, &d1, &c, &z, &v); break;
        default: break;
      };

      /* write table data to file */
      if (fputc(d1,fAludata1) == EOF)
      {
        printf("error: disk full\n");
        status = -1;
        break;
      }
    }


    /* write table data to file */
    if ((fputc(d2,fAludata2) == EOF) || (fputc(fl,fAluflags) == EOF))
    {
      printf("error: disk full\n");
      status = -1;
      break;
    }

  }

  fclose(fAluflags);
  fclose(fAludata2);
  fclose(fAludata1);
  return status;
}


#ifdef SPLITFLAGFILE

static unsigned char flag1table[0x1000];

int splitFlagFile()
{
  FILE *fAluflags, *ff1, *ff2;
  unsigned long ulAdr, adrf1;
  unsigned char b, f1, f2;
  int status = 0;

  ff1 = fopen("alu1flag.bin","wb");
  if (ff1 == NULL)
  {
    printf("failed to generate file\n");
    return -1;
  }
  ff2 = fopen("alu2flag.bin","wb");
  if (ff1 == NULL)
  {
    fclose(ff1);
    printf("failed to generate file\n");
    return -1;
  }

  fAluflags = fopen(FNAME_ALUF,"rb");
  if (fAluflags == NULL)
  {
    fclose(ff1);
    fclose(ff2);
    printf("failed to generate file\n");
    return -1;
  }

  for (ulAdr = 0; ulAdr < 0x100000; ulAdr++)
  {
    if (feof(fAluflags))
    {
      printf("unexpected end of file\n");
      status = -1;
      break;
    }
    b = fgetc(fAluflags);

    f2 = b & 0x0F;
    f1 = (b >> 4) & 0x0F;

    if (fputc(f2,ff2) == EOF)
    {
      printf("error: disk full\n");
      status = -1;
      break;
    }

    adrf1 = (ulAdr & 0xFF) | ((ulAdr & 0xF0000) >> 8);
    flag1table[adrf1] = f1;
  }

  for (ulAdr = 0; ulAdr < 0x1000; ulAdr++)
  {
    if (fputc(flag1table[ulAdr],ff1) == EOF)
    {
      printf("error: disk full\n");
      status = -1;
      break;
    }
  } 

  fclose(ff1);
  fclose(ff2);
  fclose(fAluflags);
  return status;
}
#endif /* SPLITFLAGFILE */


int generateEpromImage(int fill, const char *srcfile, const char *dstfile)
{
  FILE *sf, *df;
  unsigned char b;
  int status = 0;

  sf = fopen(srcfile,"rb");
  if (sf == NULL)
  {
    printf("failed to open file\n");
    return -1;
  }
  df = fopen(dstfile,"wb");
  if (df == NULL)
  {
    fclose(sf);
    printf("failed to generate file\n");
    return -1;
  }

  while (fill > 0)
  {
    if (fputc(255, df) == EOF)
    {
      printf("error: disk full\n");
      status = -1;
      break;
    }
    fill--;
  }

  while (status == 0)
  {
    b = fgetc(sf);
    if (feof(sf))
      break;
    if (fputc(b, df) == EOF)
    {
      printf("error: disk full\n");
      status = -1;
    }
  }

  fclose(sf);
  fclose(df);
  return status;
}


int main()
{
  printf("ALU table generator for MyCPU, version 2.1.\n");
  printf("Written by Dennis Kuschel, 05/2005.\n");
  if (generateALUtables())
  {
    printf("failed to create ALU\n");
#ifndef _LINUX
    getch();
#endif
    return 0;
  }

  if ((generateEpromImage(4096,  FNAME_ALU1D, FNAME_ALU1D64) != 0) ||
      (generateEpromImage(28672, FNAME_ALU1D, FNAME_ALU1D256) != 0))
  {
    printf("failed to create ALU\n");
#ifndef _LINUX
    getch();
#endif
    return 0;
  }

  printf("\nALU tables successfully created:\n");
  printf("ALU1DATA.BIN, ALU1DATA-16.BIN, ALU1DATA-256.BIN,\n");
  printf("ALU2DATA.BIN, ALUFLAGS.BIN\n\n");

#ifdef SPLITFLAGFILE
  if (splitFlagFile())
  {
    printf("failed to split flag file\n");
#ifndef _LINUX
    getch();
#endif
  }
#endif /* SPLITFLAGFILE */

  return 0;
}
