/****************************************************************************
 * This file compiles the microcrode stored in the header files and creates
 * three binary files to burn into the microcode EPROMS.
 * 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 mcode.out mcode.c
 */

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

#include "mcode.h"


/* Macro Definitions */

#define GET_MC1(x)  (x & 0xFF)
#define GET_MC2(x)  (((x) >> 8) & 0xFF)
#define GET_MC3(x)  (((x) >> 16) & 0xFF)
#define GET_MC4(x)  (((x) >> 24) & 0xFF)

#define SET_MC(mcindex,function) { \
   g_mcode1[mcindex] = (unsigned char)GET_MC1(function); \
   g_mcode2[mcindex] = (unsigned char)GET_MC2(function); \
   g_mcode3[mcindex] = (unsigned char)GET_MC3(function) & ~MC3_INTERN; \
   g_ctrl[mcindex] = (unsigned char)GET_MC4(function); }

#define SET_MCODE(mcindex,flags,function) \
   SET_MC((mcindex & (MCODES_PER_OP-1)) + (flags << COUNTER_LINES), function);


/* Global Variables */

unsigned char g_mcode1[OPCODE_SIZE];
unsigned char g_mcode2[OPCODE_SIZE];
unsigned char g_mcode3[OPCODE_SIZE];
unsigned char g_ctrl[OPCODE_SIZE];

FILE *g_file1, *g_file2, *g_file3;



void writeMCode(int mcindex, int flag,
                unsigned long ulFuncTrue, unsigned long ulFuncFalse)
{
  int i;
  if (!flag)
  {
    for (i=0;i<(1<<COUNT_OF_FLAGS);i++)
    {
      if (ulFuncTrue != NONE)
      {
        SET_MCODE(mcindex,i,ulFuncTrue);
      }
    }
  }
  else
  {
    for (i=0;i<(1<<COUNT_OF_FLAGS);i++)
    {
      if (i & flag)
      {
        if (ulFuncTrue != NONE)
        {
          SET_MCODE(mcindex,i,ulFuncTrue);
        }
      }
      else
      {
        if (ulFuncFalse != NONE)
        {
          SET_MCODE(mcindex,i,ulFuncFalse);
        }
      }
    }
  }
}


#define NEWMCODE3
#define NEWMCODE2a
//#define NEWMCODE2b
#define NEWMCODEFASTALU


int CodeCorrection(unsigned char opcode)
{
  unsigned short usAddr, usLastAddr, usLLastAddr, ua;
  unsigned short mcindex, flags, i;
  unsigned char MC1;

  for (flags=0;flags<(1<<COUNT_OF_FLAGS);flags++)
  {
#ifdef NEWMCODE2b
    /* Find first ALU operation. Fill previous g_mcode2-fields
       with mode of first ALU operation. */
    for (mcindex=0;mcindex<MCODES_PER_OP;mcindex++)
    {
      usAddr = mcindex + (flags << COUNTER_LINES);
      if (((g_mcode1[usAddr] & 0xf0) == OE_BPALU) ||
          ((g_mcode3[usAddr] & MC3_ALUCLOCK) != 0) ||
          ((g_ctrl[usAddr] & CTRL_ALUOP) != 0))
      {
        for (i=0;i<mcindex;i++)
        {
          ua = i + (flags << COUNTER_LINES);
          g_mcode2[ua] = g_mcode2[usAddr] & MC2_ALU_MODE_BITS;
        }
        break;
      }
    }
#endif
    /* check uOP's */
    for (mcindex=0;mcindex<MCODES_PER_OP;mcindex++)
    {
      usAddr = mcindex + (flags << COUNTER_LINES);

      /* correct the address-select-signal */
      MC1 = g_mcode1[usAddr];
      if (((MC1 & 0xF0) == OE_DATA) || ((MC1 & 0xF0) == OE_CODE) || ((MC1 & 0x0F) == CLK_WRITE_RAM))
      {
        if (mcindex>0)
        {
          g_mcode3[usLastAddr] &= ~MC3_ADR_SELECT;
          g_mcode3[usLastAddr] |= g_mcode3[usAddr] & MC3_ADR_SELECT;
        }
      }

      /* If operating with the ALU, the Carry- and ALU-mode-signals must be early
       * enough available at the ALU EPROMS. We make sure that the signals are
       * at least one cycle before the ALU operation available.
       */
      if ((g_ctrl[usAddr] & CTRL_ALUOP) && (mcindex>0))
      {
        if (mcindex == 1)
        {
          printf("Error: OP-Code %02x has an ALU operation at position 1\n",opcode);
          return -1;
        }

        if ((g_ctrl[usLastAddr] & CTRL_ALUOP) && 
            ((g_mcode2[usLastAddr] & (MC2_ALU_CARRY_SELECT | MC2_ALU_FL_REG_SELECT | MC2_ALU_MODE_SELECT)) !=
             (g_mcode2[usAddr] & (MC2_ALU_CARRY_SELECT | MC2_ALU_FL_REG_SELECT | MC2_ALU_MODE_SELECT))))
        {
          printf("Error: OP-Code %02x has two consecutive ALU operations, mcode=%d\n",opcode,mcindex);
          return -1;
        }

        g_mcode2[usLastAddr] &= ~(MC2_ALU_CARRY_SELECT | MC2_ALU_FL_REG_SELECT | MC2_ALU_MODE_SELECT);
        g_mcode2[usLastAddr] |= g_mcode2[usAddr] & (MC2_ALU_CARRY_SELECT | MC2_ALU_FL_REG_SELECT | MC2_ALU_MODE_SELECT);
#ifdef NEWMCODE2a
        if (mcindex > 2)
        {
          ua = usLastAddr - 1;
          if (((g_mcode1[ua] & 0xf0) != OE_BPALU) &&
              ((g_mcode3[ua] & MC3_ALUCLOCK) == 0) &&
              ((g_ctrl[ua] & CTRL_ALUOP) == 0))
          {
            g_mcode2[ua] &= ~MC2_ALU_MODE_BITS;
            g_mcode2[ua] |= g_mcode2[usLastAddr] &  MC2_ALU_MODE_BITS;
          }
        }
#endif
      }

      /* "set ALU flag" signal correction */
      if ((g_mcode2[usAddr] & (MC2_ALU_LOAD_FLAG_C | MC2_ALU_LOAD_FLAG_VZ)) &&
          !(g_ctrl[usAddr] & CTRL_ALUOP) && !(g_mcode3[usAddr] & MC3_MCODE_END))
      {
        if (mcindex>0)
        {
          if (g_mcode2[usLastAddr] & (MC2_ALU_LOAD_FLAG_C | MC2_ALU_LOAD_FLAG_VZ))
          {
            printf("Error: OP-Code %02x has two consecutive flag set operations, mcode=%d\n",opcode,mcindex);
            return -1;
          }
          g_mcode2[usLastAddr] &= ~MC2_ALU_FL_REG_SELECT;
          g_mcode2[usLastAddr] |= g_mcode2[usAddr] & (MC2_ALU_LOAD_FLAG_C | MC2_ALU_LOAD_FLAG_VZ | MC2_ALU_FL_REG_SELECT);
          g_mcode2[usAddr] &= ~(MC2_ALU_LOAD_FLAG_C | MC2_ALU_LOAD_FLAG_VZ);
        }
      }
      else
      if ((g_mcode2[usAddr] & (MC2_ALU_LOAD_FLAG_C | MC2_ALU_LOAD_FLAG_VZ)) &&
          (g_ctrl[usAddr] & CTRL_ALUOP) && (g_mcode1[usAddr] != OE_BPALU) &&
          (g_mcode2[usAddr] & MC2_ALU_FL_REG_SELECT) && (g_mcode3[usAddr] & MC3_MCODE_END))
      {
        printf("Error: OP-Code %02x: set flag operation with use of register, mcode=%d\n",opcode,mcindex);
        return -1;
      }

      /* Safety check:
       * NEXTOP must stand in a new line if the last command was
       * a RAM or ROM access not using the program counter address.
       * This is neccessary to make sure that the address lines have
       * the correct signal levels when fetching the next OP.
       * (must take care of address setup time)
       */
      if ((g_mcode3[usAddr] & MC3_MCODE_END) && (
          (g_mcode3[usAddr] & MC3_INC_PC) ||
          (g_mcode3[usAddr] & MC3_ADR_SELECT) ))
      {
        printf("Error: OP-Code %02x ends with an invalid NEXTOP\n",opcode);
        return -1;
      }

      /* check for invalid INC_PC commands
       * (must take care of address setup time)
       */
      if ((mcindex > 0) &&
          ((g_mcode1[usAddr] & 0xf0) == OE_CODE) &&
          !(g_mcode3[usAddr] & MC3_ADR_SELECT) &&
           (g_mcode3[usLastAddr] & MC3_INC_PC))
      {
        printf("Error: OP-Code %02x has an INC_PC with following ROM-access, mcode=%d\n",opcode,mcindex);
        return -1;
      }

      /* check for too fast RAM/ROM-accesses
       * (must take care of address setup time)
       */
      if ((mcindex > 0) &&
          (((g_mcode1[usAddr] & 0xf0) == OE_CODE) || ((g_mcode1[usAddr] & 0xf0) == OE_DATA)) &&
          (g_mcode3[usAddr] & MC3_ADR_SELECT) &&
          (((g_mcode1[usLastAddr] & 0x0f) == CLK_TEMP_ADDR_LO) || ((g_mcode1[usLastAddr] & 0x0f) == CLK_TEMP_ADDR_HI)))
      {
        printf("Error: OP-Code %02x has an invalid RAM/ROM-access using TEMP_ADDR, mcode=%d\n",opcode,mcindex);
        return -1;
      }
#ifdef NEWMCODE3
      /* check for internal / external bus accesses */
      if ((mcindex > 0) && (opcode != 0x3B/*DLY*/)) 
      {
        if (((g_mcode1[usAddr] & 0xf0) != OE_CODE) &&
            ((g_mcode1[usAddr] & 0xf0) != OE_DATA) &&
            ((g_mcode1[usAddr] & 0x0f) != CLK_WRITE_RAM) &&
            ((g_mcode1[usAddr] & 0x0f) != CLK_FETCH_OPCODE) &&
            ((g_mcode3[usLastAddr] & MC3_MCODE_END) == 0))
        {
#ifndef NEWMCODEFASTALU
          /* here: no slow bus access */
          if ((mcindex > 2) &&
              (((g_mcode3[usAddr] & MC3_ALUCLOCK) != 0) || ((g_mcode1[usAddr] & 0xf0) == OE_BPALU)) &&
               ((g_mcode3[usLastAddr] & MC3_ALUCLOCK) == 0) &&
               ((g_mcode1[usLastAddr] & 0x0f) != CLK_AREG1) &&
               ((g_mcode1[usLastAddr] & 0x0f) != CLK_AREG2) &&
                ((g_mcode2[usAddr] & MC2_ALU_MODE_BITS) == (g_mcode2[usLastAddr] & MC2_ALU_MODE_BITS)) &&
                ((g_mcode2[usAddr] & MC2_ALU_MODE_BITS) == (g_mcode2[usLastAddr-1] & MC2_ALU_MODE_BITS)))
          {
            /* the uOP is a "fast" ALU operation, thus set the "INTERN" flag */
            g_mcode3[usLastAddr] |= MC3_INTERN;
          }
          else
          if (((g_mcode1[usAddr] & 0xf0) != OE_BPALU) &&
              ((g_mcode3[usAddr] & MC3_ALUCLOCK) == 0) &&
              ((g_ctrl[usAddr] & CTRL_ALUOP) == 0))
#endif
          {
            /* the uOP is an internal operation, thus set the "INTERN" flag */
            g_mcode3[usLastAddr] |= MC3_INTERN;
          }
        }
      }
#endif
#ifdef NEWMCODE2b
      /* fill next uOP's with same ALU mode value (reduce switching bits) */
      for (i=mcindex+1;i<MCODES_PER_OP;i++)
      {
        ua = i + (flags << COUNTER_LINES);
        if (((g_mcode1[ua] & 0xf0) == OE_BPALU) ||
            ((g_mcode3[ua] & MC3_ALUCLOCK) != 0) ||
            ((g_ctrl[ua] & CTRL_ALUOP) != 0))
        {
          /* do not overwrite next ALU operation */
          break;
        }
        g_mcode2[ua] = g_mcode2[usAddr] & MC2_ALU_MODE_BITS;
      }
#endif
      /* break early */
      if ((mcindex > 0) && ((g_mcode3[usLastAddr] & MC3_MCODE_END) != 0))
      {
#ifdef NEWMCODE2b
        /* copy ALU-mode to uOP 0 because it is the next that is executed */
        g_mcode2[flags << COUNTER_LINES] = g_mcode2[usAddr] & MC2_ALU_MODE_BITS;
#endif
        break;
      }

      usLLastAddr = usLastAddr;
      usLastAddr = usAddr;
    }
  }


  /* "NEXTOP" correction (do it one clock earlier) */
  for (flags=0;flags<(1<<COUNT_OF_FLAGS);flags++)
  {
    for (mcindex=0;mcindex<MCODES_PER_OP;mcindex++)
    {
      usAddr = mcindex + (flags << COUNTER_LINES);

      if (g_mcode3[usAddr] & MC3_MCODE_END)
      {
        if (mcindex < 2)
        {
          printf("Error: OP-Code %02x: The OP-Code must have at least 3 microinstructions\n",opcode);
          return -1;
        }
        g_mcode3[usAddr]     &= ~MC3_MCODE_END;
        g_mcode3[usLastAddr] |= MC3_MCODE_END;
        break;
      }
      usLLastAddr = usLastAddr;
      usLastAddr = usAddr;
    }
    for (;mcindex<MCODES_PER_OP;mcindex++)
    {
      usAddr = mcindex + (flags << COUNTER_LINES);
      g_mcode3[usAddr] &= ~MC3_MCODE_END;
    }
  }

  return 0;
}



int writeOpToFiles(int opcode, unsigned long irqflag)
{
  unsigned long destadr;
  unsigned short i;
  int written;

  if (irqflag)
    irqflag = (1 << EPROM_IRQLINE);

  for (i=0;i<OPCODE_SIZE;i++)
  {
    destadr = (((unsigned long)opcode)<<EPROM_OPCODE) +
              (((unsigned long)(i & ((1 << COUNTER_LINES)-1)))<<EPROM_COUNTER) +
              (((((unsigned long)i) >> COUNTER_LINES) & ((1<<COUNT_OF_FLAGS)-1))<<EPROM_FLAGS) +
              irqflag;

    fseek(g_file1,(long)destadr,SEEK_SET);
    written = fwrite(g_mcode1+i,1,1,g_file1);
    if (written != 1) return -1;
    fseek(g_file2,(long)destadr,SEEK_SET);
    written = fwrite(g_mcode2+i,1,1,g_file2);
    if (written != 1) return -1;
    fseek(g_file3,(long)destadr,SEEK_SET);
    written = fwrite(g_mcode3+i,1,1,g_file3);
    if (written != 1) return -1;
  }

  return 0;
}


void initialize(void)
{
  int i;
  for (i=0;i<OPCODE_SIZE;i++)
  {
    SET_MC(i,NEXTOP);
  }
  for (i=0;i<(1<<COUNT_OF_FLAGS);i++)
  {
    SET_MCODE(0,i,FETCH);
    SET_MCODE(1,i,NEXTOP);
  }
}


void finalize(void)
{
  int i;
  for (i=0;i<OPCODE_SIZE;i++)
  {
    g_mcode1[i] ^= INVERT_MC1;
    g_mcode2[i] ^= INVERT_MC2;
    g_mcode3[i] ^= INVERT_MC3;
  }
}


int buildOpCode(int tabindex)
{
  int mcindex = 0;
  int sourceidx = 1;
  int flag;
  int lastsrcidx = NO_OF_MCODE;
  unsigned long ulFunc1, ulFunc2;
  unsigned char opcode;

  opcode = (unsigned char)g_ulMCTAB[tabindex][0];

  /* initialize opcode destination memory */
  initialize();

  /* get size of source code for OP-code */
  while ((lastsrcidx>1) && !(g_ulMCTAB[tabindex][lastsrcidx] & NEXTOP))
    lastsrcidx--;

  /* assemble the microcode for the OP */
  do
  {
    if (mcindex >= MCODES_PER_OP)
    {
      printf("Error: microcode too long\n");
      return -1;
    }
    flag = (g_ulMCTAB[tabindex][sourceidx] >> 24) & 0x07;
    ulFunc1 = g_ulMCTAB[tabindex][sourceidx];
    ulFunc2 = g_ulMCTAB[tabindex][sourceidx+1];
    writeMCode(mcindex, flag, ulFunc1, ulFunc2);
    if (flag) sourceidx++;
    mcindex++;
  }
  while (++sourceidx <= lastsrcidx);

  /* check and correct the microcode */
  if (CodeCorrection(opcode))
    return -1;

  /* finalize the OP-code */
  finalize();

  /* write OP-code into EPROM binaries */
  if (writeOpToFiles(opcode,0))
    return -1;

  return mcindex;
}


int buildMicrocode(void)
{
  int tabindex;
  int ret, opcode, i, j, ops=0;
  unsigned long ulMClength=0;
  unsigned long flags, mcindex;
  unsigned long ulIRQ[MCODES_PER_OP];
  unsigned char buildOPCodes[256];

  /* build the OP-codes */
  printf("Building OP-Codes, please wait...\n");
  memset(buildOPCodes,0,256);
  for (tabindex=0;tabindex<NO_OF_OPCODES;tabindex++)
  {
    opcode = g_ulMCTAB[tabindex][0];
    printf("\rOP-Code %02x  ",opcode);
#ifdef _LINUX
    fflush(stdout);
#endif
    if (buildOPCodes[opcode])
    {
      printf("Error: this OP-Code has already been build\n");
      return -1;
    }
    ret = buildOpCode(tabindex);
    if (ret<0) return -1;
    ulMClength += ret;
    buildOPCodes[opcode] = 1;
    ops++;
  }

  printf("\rAverage OP-Code length: %.2f microcodes\n",((float)ulMClength)/NO_OF_OPCODES);
  printf("\nBuilding the interrupt code, please wait...\n");

  /* build interrupt code */
  j = 0;
  for (i=0;i<MCODES_PER_OP;i++)
  {
    if (!j)
    {
      if ((g_ulMCIRQ[i] >> 24) & 0x07)
      {
        printf("Error: use of flags in interrupt-code not allowed\n");
        return -1;
      }
      ulIRQ[i] = g_ulMCIRQ[i];
      if (g_ulMCIRQ[i] & NEXTOP)
        j=1;
    }
    else
    {
      if (j==1)
        ulIRQ[i] = NEXTOP;
      else
        ulIRQ[i] = 0xFF;
      j=2;
    }
  }

  /* initialize opcode destination memory */
  initialize();

  /* build interrupt code stamp */
  for (flags=0;flags<(1<<COUNT_OF_FLAGS);flags++)
  {
    for (mcindex=0;mcindex<MCODES_PER_OP;mcindex++)
    {
      SET_MCODE(mcindex,flags,ulIRQ[mcindex]);
    }
  }

  /* check and correct the microcode */
  if (CodeCorrection(0))
    return -1;

  /* finalize the interrupt-code */
  finalize();

  /* copy the interrupt code to all OPs */
  for (i=0;i<256;i++)
  {
    printf("\rOP-Code %02x  ",i);
#ifdef _LINUX
    fflush(stdout);
#endif
    if (writeOpToFiles(i,1))
      return -1;
  }

  /* build dummy OP-codes */
  printf("\r           \n");
  if (ops<256)
  {
    printf("\r%i possible opcodes not used, building dummys...\n",256-ops);
    initialize();
    for (i=0;i<256;i++)
    {
      if (buildOPCodes[i])
        continue;
      printf("\rOP-Code %02x  ",i);
#ifdef _LINUX
      fflush(stdout);
#endif
      if (writeOpToFiles(i,0))
        return -1;
    }
    printf("\r           \n");
  }

  printf("All built.\n");
  return 0;
}


void printMicrocode(unsigned char opcode, unsigned long flags)
{
  unsigned long ulAddr;
  unsigned long mcindex;
  char szBin1[10], szBin2[10];
  int i, q;
  unsigned char b, b1,b2,b3;

  printf("\nMicrocode-listing for OP-Code %02x, C=%lu, Z=%lu, V=%lu\n\n",
         opcode,flags & FLAG_C, (flags & FLAG_Z)>>1, (flags & FLAG_V)>>2);

  q = 0;
  for (mcindex=0;mcindex<=NO_OF_MCODE;mcindex++)
  {
    ulAddr = opcode + (flags<<8) + (mcindex<<(COUNT_OF_FLAGS+1+8));//+(1<<EPROM_IRQLINE);
    fseek(g_file1,(long)ulAddr,SEEK_SET);
    fread(&b1,1,1,g_file1);
    fseek(g_file2,(long)ulAddr,SEEK_SET);
    fread(&b2,1,1,g_file2);
    fseek(g_file3,(long)ulAddr,SEEK_SET);
    fread(&b3,1,1,g_file3);
    b1 ^= INVERT_MC1;
    b2 ^= INVERT_MC2;
    b3 ^= INVERT_MC3;
    b=b2;
    for (i=0;i<8;i++)
      { if (b & 0x80) szBin1[i]='1'; else szBin1[i]='0'; b <<= 1; }
    szBin1[i]=0;
    b=b3;
    for (i=0;i<8;i++)
      { if (b & 0x80) szBin2[i]='1'; else szBin2[i]='0'; b <<= 1; }
    szBin2[i]=0;
    printf("[%02lu]   ",mcindex);
    if (b1)
    {
      if (!(b1&0xF0))
        printf("clk: %01x",(int)(b1 & 0x0F));
      else
        printf("%01x -> %01x",(int)((b1>>4) & 0x0F), (int)(b1 & 0x0F));
    }
    else
    {
      printf("      ");
    }
    if (b3 & MC3_ALUCLOCK)
      printf("   ALU-mode %01x",(int)(b2 & 0x0F));
    else
      printf("             ");
    printf("   mcode2[7..0]=%s   mcode3=%s\n",szBin1, szBin2);
    if (q)
      break;
    if (b3 & MC3_MCODE_END)
      q = 1;
  }
}


int closeFiles(void)
{
  if (g_file1)
    fclose(g_file1);
  if (g_file2)
    fclose(g_file2);
  if (g_file3)
    fclose(g_file3);
  return -1;
}


int allocateFileSpace(FILE *ffile, unsigned long space)
{
  unsigned long i;
  int written;
  unsigned char buf[256];
  memset(buf,0xFF,256);
  space = space >> 8;
  for (i=0;i<space;i++)
  {
    written = fwrite(buf,1,256,ffile);
    if (written != 256)
      return closeFiles();
  }
  return 0;
}


int openFiles(void)
{
  unsigned long ulEpromSize;
  g_file1 = fopen("mcode1.bin","w+b");
  g_file2 = NULL;
  g_file3 = NULL;
  if (!g_file1)
    return closeFiles();
  g_file2 = fopen("mcode2.bin","w+b");
  if (!g_file2)
    return closeFiles();
  g_file3 = fopen("mcode3.bin","w+b");
  if (!g_file3)
    return closeFiles();
  ulEpromSize = (1 << (COUNTER_LINES+COUNT_OF_FLAGS+1+8));
  if (allocateFileSpace(g_file1, ulEpromSize))
    return -1;
  if (allocateFileSpace(g_file2, ulEpromSize))
    return -1;
  if (allocateFileSpace(g_file3, ulEpromSize))
    return -1;
  return 0;
}


int main()
{
  printf("\nOP-Code-Compiler V2.2  by Dennis Kuschel\n\n");
  if (openFiles())
  {
    printf("Error: Failed to open output files\n");
  }
  else
  {
    buildMicrocode();

#if 1
    printMicrocode(0x4B, FLAG_C);  /* insert this line to view the microcode of an OP-Code*/
#endif
    closeFiles();
  }
#ifndef _LINUX
  printf("\nPress any key to continue\n");
  getch();
#endif
  return 0;
}
