/* PIC Processor selection
   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
   James Bowman, Craig Franklin

    Copyright (C) 2014-2016 Molnar Karoly

This file is part of gputils.

gputils 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, or (at your option)
any later version.

gputils 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 gputils; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "stdhdr.h"

#include "libgputils.h"
#include "gpasm.h"
#include "directive.h"
#include "gpmsg.h"

/*------------------------------------------------------------------------------------------------*/

static void
_make_bank_constants_and_variables(pic_processor_t Proc, int Num_of_banks)
{
  int  bank_size;
  int  bank_addr;
  int  i;
  char name[64];

  i = (Num_of_banks == 1) ? 0 : __BANK_INV;

  set_global(__ACTIVE_BANK_ADDR, i, VAL_VARIABLE, true, false);
  set_global(__ASSUMED_BANK_ADDR, i, VAL_VARIABLE, true, false);

  if (Num_of_banks <= 0) {
    return;
  }

  set_global("__NUM_BANKS", Num_of_banks, VAL_CONSTANT, true, false);

  bank_size = Proc->class->bank_size;
  bank_addr = 0;
  i         = 0;
  while (true) {
    if (i == 0) {
      set_global("__BANK_FIRST", bank_addr, VAL_CONSTANT, true, false);
    }

    snprintf(name, sizeof(name), "__BANK_%i", i);
    set_global(name, bank_addr, VAL_CONSTANT, true, false);
    ++i;

    if (i == Num_of_banks) {
      set_global("__BANK_LAST", bank_addr, VAL_CONSTANT, true, false);
      break;
    }

    bank_addr += bank_size;
  }
}

/*------------------------------------------------------------------------------------------------*/

static void
_make_page_constants_and_variables(pic_processor_t Proc, int Num_of_pages)
{
  int  page_size;
  int  page_addr;
  int  i;
  char name[64];

  i = (Num_of_pages == 1) ? 0 : __PAGE_INV;

  set_global(__ACTIVE_PAGE_ADDR, i, VAL_VARIABLE, true, false);

  if (Num_of_pages <= 0) {
    return;
  }

  set_global("__NUM_PAGES", Num_of_pages, VAL_CONSTANT, true, false);

  page_size = Proc->class->page_size;
  page_addr = 0;
  i         = 0;
  while (true) {
    if (i == 0) {
      set_global("__PAGE_FIRST", page_addr, VAL_CONSTANT, true, false);
    }

    snprintf(name, sizeof(name), "__PAGE_%i", i);
    set_global(name, page_addr, VAL_CONSTANT, true, false);
    ++i;

    if (i == Num_of_pages) {
      set_global("__PAGE_LAST", page_addr, VAL_CONSTANT, true, false);
      break;
    }

    page_addr += page_size;
  }
}

/*------------------------------------------------------------------------------------------------*/

void
select_processor(const char *Name)
{
  pic_processor_t  found;
  proc_class_t     class;
  const int       *pair;
  const vector_t  *vec;
  unsigned int     num;
  int              addr;
  int              num_of_banks;
  int              num_of_pages;
  char             buf[BUFSIZ];
  int              badrom_idx;
  long             start;
  long             end;

  if (state.cmd_line.processor) {
    gpmsg_vwarning(GPW_CMDLINE_PROC, NULL);
  }
  else {
    found = gp_find_processor(Name);

    if (found != NULL) {
      if (state.processor == NULL) {
        /* If in extended mode: Check if processor supports extended instruction set. */
        if (state.extended_pic16e && !(found->cpu_flags & CPU_HAVE_EXTINST)) {
          gpmsg_verror(GPE_NO_EXTENDED_MODE, NULL);
        }

        state.processor = found;
        state.maxram    = found->maxram;
        state.maxrom    = found->maxrom;

        if (state.maxrom > 0) {
          /* Initialize badrom from internal processor info. */
          gp_bitarray_create(&state.badrom, state.maxrom);

          for (badrom_idx = 0; badrom_idx < MAX_BADROM; badrom_idx += 2) {
            start = found->badrom[badrom_idx];
            end   = found->badrom[badrom_idx + 1];

            if ((start >= 0) && (end >= start)) {
              gp_bitarray_write_range(&state.badrom, start, end, true);
            }
          }
        }

        set_global(found->defined_as, 1, VAL_CONSTANT, false, false);

        if (!state.mpasm_compatible) {
          class = found->class;

          num_of_banks = gp_processor_num_banks(found);
          num_of_pages = gp_processor_num_pages(found);

          if ((class == PROC_CLASS_GENERIC) || (class == PROC_CLASS_PIC12) ||
              (class == PROC_CLASS_PIC12E)  || (class == PROC_CLASS_PIC12I)) {
            if ((class == PROC_CLASS_PIC12E) || (class == PROC_CLASS_PIC12I)) {
              set_global("__ENHANCED", 1,                 VAL_CONSTANT, true, false);
            }

            set_global("__12_BIT",     1,                 VAL_CONSTANT, true, false);
            set_global("__PAGE_BITS",  PIC12_PAGE_BITS,   VAL_CONSTANT, true, false);
            set_global("__PAGE_MASK",  class->page_mask,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SHIFT", PIC12_PAGE_SHIFT,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SIZE",  class->page_size,  VAL_CONSTANT, true, false);
            set_global("__PAGE_INV",   __PAGE_INV,        VAL_CONSTANT, true, false);

            _make_bank_constants_and_variables(found, num_of_banks);
            _make_page_constants_and_variables(found, num_of_pages);
          }
          else if (class == PROC_CLASS_SX) {
            set_global("__12_BIT",     1,                 VAL_CONSTANT, true, false);
            set_global("__PAGE_BITS",  SX_PAGE_BITS,      VAL_CONSTANT, true, false);
            set_global("__PAGE_MASK",  class->page_mask,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SHIFT", PIC12_PAGE_SHIFT,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SIZE",  class->page_size,  VAL_CONSTANT, true, false);
            set_global("__PAGE_INV",   __PAGE_INV,        VAL_CONSTANT, true, false);

            _make_bank_constants_and_variables(found, num_of_banks);
            _make_page_constants_and_variables(found, num_of_pages);
          }
          else if (class == PROC_CLASS_PIC14) {
            set_global("__14_BIT",     1,                 VAL_CONSTANT, true, false);
            set_global("__PAGE_BITS",  PIC14_PAGE_BITS,   VAL_CONSTANT, true, false);
            set_global("__PAGE_MASK",  class->page_mask,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SHIFT", PIC14_PAGE_SHIFT,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SIZE",  class->page_size,  VAL_CONSTANT, true, false);
            set_global("__PAGE_INV",   __PAGE_INV,        VAL_CONSTANT, true, false);

            _make_bank_constants_and_variables(found, num_of_banks);
            _make_page_constants_and_variables(found, num_of_pages);
          }
          else if ((class == PROC_CLASS_PIC14E) || (class == PROC_CLASS_PIC14EX)) {
            set_global("__14_BIT",        1,                    VAL_CONSTANT, true, false);
            set_global("__ENHANCED",      1,                    VAL_CONSTANT, true, false);
            set_global("__PAGE_BITS",     PIC14E_PAGE_BITS,     VAL_CONSTANT, true, false);
            set_global("__PAGE_MASK",     class->page_mask,     VAL_CONSTANT, true, false);
            set_global("__PAGE_SHIFT",    PIC14_PAGE_SHIFT,     VAL_CONSTANT, true, false);
            set_global("__PAGE_SIZE",     class->page_size,     VAL_CONSTANT, true, false);
            set_global("__PAGE512_SHIFT", PIC14E_PAGE512_SHIFT, VAL_CONSTANT, true, false);
            set_global("__PAGE_INV",      __PAGE_INV,           VAL_CONSTANT, true, false);

            _make_bank_constants_and_variables(found, num_of_banks);
            _make_page_constants_and_variables(found, num_of_pages);
          }
          else if (class == PROC_CLASS_PIC16) {
            set_global("__16_BIT",     1,                 VAL_CONSTANT, true, false);
            set_global("__PAGE_BITS",  PIC16_PAGE_BITS,   VAL_CONSTANT, true, false);
            set_global("__PAGE_MASK",  class->page_mask,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SHIFT", PIC16_PAGE_SHIFT,  VAL_CONSTANT, true, false);
            set_global("__PAGE_SIZE",  class->page_size,  VAL_CONSTANT, true, false);
            set_global("__PAGE_INV",   __PAGE_INV,        VAL_CONSTANT, true, false);

            _make_bank_constants_and_variables(found, num_of_banks);
            _make_page_constants_and_variables(found, num_of_pages);
          }
          else if (class == PROC_CLASS_PIC16E) {
            set_global("__16_BIT",   1, VAL_CONSTANT, true, false);
            set_global("__EXTENDED", 1, VAL_CONSTANT, true, false);

            _make_bank_constants_and_variables(found, num_of_banks);
          }

          set_global("__BANK_BITS",  found->bank_bits,       VAL_CONSTANT, true, false);
          set_global("__BANK_MASK",  class->bank_mask,       VAL_CONSTANT, true, false);
          set_global("__BANK_SHIFT", class->bank_bits_shift, VAL_CONSTANT, true, false);
          set_global("__BANK_SIZE",  class->bank_size,       VAL_CONSTANT, true, false);
          set_global("__BANK_INV",   __BANK_INV,             VAL_CONSTANT, true, false);

          if ((pair = gp_processor_common_ram_exist(found)) != NULL) {
            set_global("__COMMON_RAM_START", pair[0], VAL_CONSTANT, true, false);
            set_global("__COMMON_RAM_END",   pair[1], VAL_CONSTANT, true, false);
          }

          if (found->common_ram_max > 0) {
            set_global("__COMMON_RAM_MAX", found->common_ram_max, VAL_CONSTANT, true, false);
          }

          if ((pair = gp_processor_linear_ram_exist(found)) != NULL) {
            set_global("__LINEAR_RAM_START", pair[0], VAL_CONSTANT, true, false);
            set_global("__LINEAR_RAM_END",   pair[1], VAL_CONSTANT, true, false);
          }

          if ((class->vector_table != NULL) || (class->vector_number > 0)) {
            vec = class->vector_table;
            num = class->vector_number;

            for (; num; ++vec, --num) {
              buf[0] = '_';
              buf[1] = '_';
              gp_stptoupper(&buf[2], vec->name, sizeof(buf) - 2);

              if (vec->address < 0) {
                /* This a SX type processor. */
                addr = found->prog_mem_size;

                if (addr > 0) {
                  --addr;
                }
              }
              else {
                addr = vec->address;
              }

              set_global(buf, addr, VAL_CONSTANT, true, false);
            }
          }

          addr = found->prog_mem_size;

          if (addr > 0) {
            set_global("__CODE_START",        0, VAL_CONSTANT, true, false);
            set_global("__CODE_END",   addr - 1, VAL_CONSTANT, true, false);
          }

          if ((pair = gp_processor_idlocs_exist(found)) != NULL) {
            set_global("__IDLOCS_START", pair[0], VAL_CONSTANT, true, false);
            set_global("__IDLOCS_END",   pair[1], VAL_CONSTANT, true, false);
          }

          if ((pair = gp_processor_config_exist(found)) != NULL) {
            set_global("__CONFIG_START", pair[0], VAL_CONSTANT, true, false);
            set_global("__CONFIG_END",   pair[1], VAL_CONSTANT, true, false);
          }

          if ((pair = gp_processor_eeprom_exist(found)) != NULL) {
            set_global("__EEPROM_START", pair[0], VAL_CONSTANT, true, false);
            set_global("__EEPROM_END",   pair[1], VAL_CONSTANT, true, false);
          }

          addr = gp_processor_bsr_boundary(found);

          if (addr > 0) {
            set_global("__ACC_RAM_LOW_START",        0, VAL_CONSTANT, true, false);
            set_global("__ACC_RAM_LOW_END",   addr - 1, VAL_CONSTANT, true, false);
          }
        } /* if (!state.mpasm_compatible) */
      } /* if (state.processor == NULL) */
      else if (state.processor != found) {
        gpmsg_vwarning(GPW_REDEFINING_PROC, NULL);
        gpmsg_verror(GPE_EXTRA_PROC, NULL);
      }
    }
    else {
      if (state.pass > 0) {
        gpmsg_verror(GPE_UNKNOWN_PROC, NULL, Name);
      }
      else {
        printf("Didn't find any processor named: %s\nHere are the supported processors:\n", Name);
        gp_dump_processor_list(true, PROC_CLASS_UNKNOWN, PROC_CLASS_UNKNOWN, PROC_CLASS_UNKNOWN);
        exit(1);
      }
    }

    /* Load the instruction sets if necessary. */
    if ((!state.processor_chosen) && (state.processor != NULL)) {
      opcode_init(1);   /* General directives. */
      /* Separate the directives from the opcodes. */
      state.stBuiltin = gp_sym_push_table(state.stBuiltin, true);
      opcode_init(2);   /* Processor-specific. */

      if (!(IS_PIC16_CORE) && !(IS_PIC16E_CORE)) {
        opcode_init(3);   /* Special pseudo ops for 12 and 14 bit devices. */
      }

      state.processor_chosen = true;
    }
  }
}
