/* cpu.c tr3200 cpu description file */
/* (c) in 2014,2019 by Luis Panadero Guardeno */

#include "vasm.h"

/*#define CPU_DEBUG (1)*/
#ifdef CPU_DEBUG
#define OPERAND_DEBUG (1)
#define INSTR_DEBUG (1)
#endif

const char *cpu_copyright="vasm TR3200 cpu module v0.2 by Luis Panadero Guardeno";

const char *cpuname="tr3200";
int bytespertaddr=4;

mnemonic mnemonics[]={
#include "opcodes.h"
};

const int mnemonic_cnt=sizeof(mnemonics)/sizeof(mnemonics[0]);


static taddr opsize(operand *p, unsigned char num_operands, section *sec, taddr pc);

/* parse instruction */
char *parse_instruction(char *s, int *inst_len, char **ext, int *ext_len,
                        int *ext_cnt)
{
/*  char* inst = s;*/
#ifdef CPU_DEBUG
    fprintf(stderr, "parse_inst : \"%.*s\"\n", *inst_len, s);
#endif
/*
  while (*s && !isspace((unsigned char)*s))
    s++;
  *inst_len = s - inst; */
  return s;
}

/* Sets op if is a valid register */
static int parse_reg(char **p, int len, operand *op)
{
  char *rp = skip(*p);
  int reg = -1;

  if (len < 2) {
    return 0;
  }

  if (*rp != '%') {
    return 0;
  }
  rp++;

  if (tolower((unsigned char)rp[0]) != 'r') {
    /* Could be y, bp, sp, ia or flags */
    if (len == 2 && tolower((unsigned char)rp[0]) == 'y') {
      rp++;
      *p = skip(rp);
      op->type = OP_GPR;
      op->reg = 11;

      return 1;
    } else if ( len == 3 && (tolower((unsigned char)rp[0]) == 'b')
        && (tolower((unsigned char)rp[1]) == 'p')  ) {
      rp++; rp++;
      *p = skip(rp);
      op->type = OP_GPR;
      op->reg = 12;

      return 1;
    } else if ( len == 3 && (tolower((unsigned char)rp[0]) == 's')
        && (tolower((unsigned char)rp[1]) == 'p')  ) {
      rp++; rp++;
      *p = skip(rp);
      op->type = OP_GPR;
      op->reg = 13;

      return 1;
    } else if ( len == 3 && (tolower((unsigned char)rp[0]) == 'i')
        && (tolower((unsigned char)rp[1]) == 'a')  ) {
      rp++; rp++;
      *p = skip(rp);
      op->type = OP_GPR;
      op->reg = 14;

      return 1;
    } else if ( len == 6
        && (tolower((unsigned char)rp[0]) == 'f')
        && (tolower((unsigned char)rp[1]) == 'l')
        && (tolower((unsigned char)rp[2]) == 'a')
        && (tolower((unsigned char)rp[3]) == 'g')
        && (tolower((unsigned char)rp[4]) == 's')
        ) {
      rp += 5;
      *p = skip(rp);
      op->type = OP_GPR;
      op->reg = 14;

      return 1;
    }
    /* It's not a register */
    return 0;
  }

  rp++;
  /* Get number */
  if (len < 2 || sscanf(rp, "%d", &reg) != 1) {
    return 0;
  }

  /* "%r0 .. %r15" are valid */
  if (reg < 0 || reg > 15) {
    return 0;
  }

  /* skip digits and return new pointer together with register number */
  while ( isdigit((unsigned char)*rp) ) {
    rp++;
  }

  *p = skip(rp);
  op->type = OP_GPR;
  op->reg = reg;
  return 1;
}

/* Parses operands and reads expressions
 * *p string
 * len string length
 * *op operand
 * requires Type of operand expected
 */
int parse_operand(char *p, int len, operand *op, int requires)
{
  op->type = NO_OP;
#ifdef OPERAND_DEBUG
    fprintf(stderr, "parse_operand (reqs=%02x): \"%.*s\"\t",
           (unsigned)requires, len, p);
#endif

  /* Try to grab the register */
  if (1 != parse_reg(&p, len, op) ) {
#ifdef OPERAND_DEBUG
    fprintf(stderr, "imm\t");
#endif
    /* Its not a register, should be a immediate value or a expression */
    if(p[0]=='#') { /* Immediate value */
      expr *tree;
#ifdef OPERAND_DEBUG
    fprintf(stderr, "# ");
#endif
      op->type = OP_IMM;
      p=skip(p+1);
      tree = parse_expr(&p);
      if (!tree) { /* It's not a valid expression */
        return PO_NOMATCH;
      }
      op->value = tree;
    } else { /* expression that would be a immediate value */
      /*int parent=0;*/
      expr *tree;
#ifdef OPERAND_DEBUG
    fprintf(stderr, "expr\t");
#endif
      op->type = OP_IMM;

      /*
      if (*p=='(') {
        parent=1;
        p=skip(p+1);
      }
      */

      tree = parse_expr(&p);
      if (!tree) { /* It's not a valid expression */
        return PO_NOMATCH ;
      }

      /* Inside of a ( ) */
      /*
      p=skip(p);
      if(parent) {
        if(*p!=')'){
          cpu_error(0);
          return 0;
        } else
        p=skip(p+1);
      }
      */

      op->value=tree;
    }
  }

#ifdef OPERAND_DEBUG
    fprintf(stderr, "(type=%02x)\n", (unsigned) op->type);
#endif

  if(requires == op->type) { /* Matched type */
    return PO_MATCH;
  }

  return PO_NOMATCH; /* Ops! Not match */
}

/* Convert an instruction into a DATA atom including relocations,
   if necessary. */
dblock *eval_instruction (instruction *p, section *sec, taddr pc)
{
  /* Calc instruction size */
  size_t size = instruction_size(p, sec, pc);
  dblock *db = new_dblock();
  mnemonic m = mnemonics[p->code];
  unsigned char *d; /* Data */
  taddr val;
  unsigned char ml_bits = 0;
  unsigned char num_operands = 0;
  unsigned char srn = 0; /* Size of Rn */
  operand* rn = NULL;
  symbol *base = NULL;
  int btype;

#ifdef INSTR_DEBUG
  fprintf(stderr, "eval_instruction code \"%s\" ", m.name);
#endif

  num_operands += (m.operand_type[0] != NO_OP)? 1 : 0;
  num_operands += (m.operand_type[1] != NO_OP)? 1 : 0;
  num_operands += (m.operand_type[2] != NO_OP)? 1 : 0;
  if (num_operands > 0) {
    rn = p->op[m.ext.rn_pos];
  }

#ifdef INSTR_DEBUG
  fprintf(stderr, "P%d ", num_operands);
  if (num_operands > 0)
    fprintf(stderr, "rn type=%02x ", (unsigned) rn->type);
#endif

  /* See if Rn is an immediate to set ML bits*/
  if (rn != NULL && rn->type == OP_IMM) {
    srn = opsize(rn, num_operands, sec, pc);
    ml_bits = 2 | (srn == 4);
  }
  ml_bits = ml_bits << 6; /* Emplace ML bits */
#ifdef INSTR_DEBUG
  fprintf(stderr, "IMM=%d ", srn);
  fprintf(stderr, "ml %02x ", ml_bits);
#endif

  db->size=size;
  db->data = mymalloc(size); /* allocate for the data block */
  memset(db->data, 0, db->size);
  /* Here to write data ! */
  d = db->data;
  d[3] = m.ext.opcode; /* Common part */
  d[2] = ml_bits;

  switch (num_operands) {
    case 3: /* format P3 */
      /* Rn is always at the LSBytes side */
      if( rn->type == OP_GPR) { /* Is a register */
        d[0] = (rn->reg) & 0xF;
      } else if (ml_bits == 0x80 ) { /* ML are 10 -> immediate */
        eval_expr(rn->value, &val, sec, pc);
#ifdef INSTR_DEBUG
    fprintf(stderr, "val %04x ", val);
#endif
        d[1] = (val >>8)  & 0x3F;
        d[0] = (val )     & 0xFF;
      } else { /* 32 bit immediate */
        eval_expr(rn->value, &val, sec, pc);
#ifdef INSTR_DEBUG
    fprintf(stderr, "val %08x ", val);
#endif
        d[7] = (val >>24) & 0xFF;
        d[6] = (val >>16) & 0xFF;
        d[5] = (val >>8 ) & 0xFF;
        d[4] = (val )     & 0xFF;
      }

      if (m.ext.rn_pos == 1) { /* Special case of STORE */
        d[1] |= (p->op[0]->reg & 0xF) << 6;
        d[2] |= (p->op[0]->reg & 0xF) >> 2;
        d[2] |= (p->op[2]->reg) << 2; /* rd */
      } else {
        d[1] |= (p->op[1]->reg & 0xF) << 6;
        d[2] |= (p->op[1]->reg & 0xF) >> 2;
        d[2] |= (p->op[0]->reg) << 2; /* rd */
      }

      break;

    case 2: /* format P2 */
      if( rn->type == OP_GPR) { /* Is a register */
        d[0] = (rn->reg) & 0xF;
      } else if (ml_bits == 0x80 ) { /* ML are 10 -> immediate */
        eval_expr(rn->value, &val, sec, pc);
        /* CALL/JUMP stuff */
        if (m.ext.opcode == 0x4B || m.ext.opcode == 0x4C ) {
          val = val >> 2; /* CALL/JMP does a left shift of two bits */
        }
#ifdef INSTR_DEBUG
    fprintf(stderr, "val %06x ", val);
#endif
        d[2] |= (val >>16)  & 0x03;
        d[1] =  (val >>8)  & 0xFF;
        d[0] =  (val )     & 0xFF;

      } else { /* 32 bit immediate */
        eval_expr(rn->value, &val, sec, pc);
#ifdef INSTR_DEBUG
    fprintf(stderr, "val %08x ", val);
#endif
        d[7] = (val >>24) & 0xFF;
        d[6] = (val >>16) & 0xFF;
        d[5] = (val >>8 ) & 0xFF;
        d[4] = (val )     & 0xFF;
      }

      if (m.ext.rn_pos == 0) { /* Special case of STORE */
        d[2] |= (p->op[1]->reg) << 2;
      } else {
        d[2] |= (p->op[0]->reg) << 2;
      }
      break;

    case 1: /* format P1 */
      if( rn->type == OP_GPR) { /* Is a register */
        d[0] = (rn->reg) & 0xF;
      } else if (ml_bits == 0x80 ) { /* ML are 10 -> immediate */
        if (!eval_expr(rn->value, &val, sec, pc))
          btype = find_base(rn->value, &base, sec, pc);
        /* CALL/JUMP stuff */
        if (m.ext.opcode >= 0x27 && m.ext.opcode <= 0x28 ) {
          if ((base != NULL && btype == BASE_OK && !is_pc_reloc(base, sec)) ||
              base == NULL)
            val = val - pc - 4; /* Relative jump/call (%pc has been increased) */
          else if (btype == BASE_OK)
            add_extnreloc_masked(&db->relocs, base, val-4, REL_PC,
                                 0, 22, 0, 0xfffffc);
          else
            general_error(38);  /* @@@ illegal relocation */
          base = NULL;
          val = val >> 2; /* CALL/JMP does a left shift of two bits */
        } else if (m.ext.opcode >= 0x25 && m.ext.opcode <= 0x26 ) {
          if (base != NULL && btype != BASE_ILLEGAL) {
            add_extnreloc_masked(&db->relocs, base, val,
                                 btype == BASE_PCREL ? REL_PC : REL_ABS,
                                 0, 22, 0, 0xfffffc);
            base = NULL;
          }
        }

#ifdef INSTR_DEBUG
    fprintf(stderr, "val %06x ", val);
#endif
        d[2] |= (val >>16)  & 0x3F;
        d[1] =  (val >>8)  & 0xFF;
        d[0] =  (val )     & 0xFF;

      } else { /* 32 bit immediate */
        eval_expr(rn->value, &val, sec, pc);
        /* CALL/JUMP stuff */
        if (m.ext.opcode >= 0x27 && m.ext.opcode <= 0x28 ) {
          val = val - pc - 4; /* Relative jump/call */
        }
#ifdef INSTR_DEBUG
    fprintf(stderr, "val %08x ", val);
#endif
        d[7] = (val >>24) & 0xFF;
        d[6] = (val >>16) & 0xFF;
        d[5] = (val >>8 ) & 0xFF;
        d[4] = (val )     & 0xFF;
      }

      break;

    case 0: /* format NP */
#ifdef INSTR_DEBUG
    fprintf(stderr, "NP ");
#endif
    default:
    ;
  }
#ifdef INSTR_DEBUG
  {
    int i;
    for(i= db->size -1; i >= 0; i-- ) {
      fprintf(stderr, "%02X ", d[i]);
    }
  }
  fprintf(stderr, "\n");
#endif

  return db;
}

/* Create a dblock (with relocs, if necessary) for size bits of data. */
dblock *eval_data(operand *op, size_t bitsize, section *sec, taddr pc)
{
  dblock *new=new_dblock();
  taddr val;
  new->size = bitsize >> 3;
  new->data = mymalloc(new->size);
#ifdef CPU_DEBUG
    fprintf(stderr, "eval_data ");
#endif
  if(op->type != OP_IMM) { /* ??? */
#ifdef CPU_DEBUG
    fprintf(stderr, "!= OP_IMM\n");
#endif
    ierror(0);
  }

  if(bitsize!=8 && bitsize!=16 && bitsize!=32) {
#ifdef CPU_DEBUG
    fprintf(stderr, "bad data size\n");
#endif
    cpu_error(2); /* Invalid data size */
  }

  if(!eval_expr(op->value, &val, sec, pc) ) {
    symbol *base;
    int btype;

    btype = find_base(op->value, &base, sec, pc);
    if (base)
      add_extnreloc(&new->relocs, base, val,
                    btype==BASE_PCREL ? REL_PC : REL_ABS, 0, bitsize, 0);
    else
      general_error(38);  /* illegal relocation */
  }

  if(bitsize == 8){
    new->data[0] = val & 0xFF;

  } else if (bitsize == 16){
    new->data[1] = (val>>8)  & 0xFF;
    new->data[0] = val       & 0xFF;

  } else if (bitsize == 32){
    new->data[3] = (val>>24) & 0xFF;
    new->data[2] = (val>>16) & 0xFF;
    new->data[1] = (val>>8)  & 0xFF;
    new->data[0] = val       & 0xFF;
  }
  return new;
}

/* Size of a operand
 * *p operand
 * num_operand Total number of operands
 * */
static taddr opsize(operand *p, unsigned char num_operands, section *sec, taddr pc)
{
  taddr val = 0;
  if(!p) {
    return 0;

  } else if (p->type == OP_IMM ) {
    eval_expr(p->value, &val, sec, pc);
    if (num_operands == 3
        && (val < -8192 || val > 8191) ) {          /* 14 bits */
      return 4; /* 32 bit immediate */
    } else if (num_operands == 2
        && (val < -131072 || val > 131071) ) {  /* 18 bits */
      return 4; /* 32 bit immediate */
    } else if (num_operands == 1
        && (val < -2097152 || val > 2097151) ) { /* 22 bits */
      return 4; /* 32 bit immediate */
    }
  }
  return 0;
}

/* Calculate the size of the current instruction; must be identical
   to the data created by eval_instruction. */
size_t instruction_size (instruction *p, section *sec, taddr pc)
{
  size_t size = 4; /* four bytes */
  unsigned char num_operands = 0;
  num_operands += (p->op[0] != 0)? 1 : 0;
  num_operands += (p->op[1] != 0)? 1 : 0;
  num_operands += (p->op[2] != 0)? 1 : 0;

  size += opsize(p->op[0], num_operands, sec, pc);
  size += opsize(p->op[1], num_operands, sec, pc);
  size += opsize(p->op[2], num_operands, sec, pc);

  return size;
}

operand *new_operand(void)
{
  operand *new = mymalloc(sizeof(*new));
  new->type=-1;
  return new;
}

/* return true, if initialization was successful */
int init_cpu(void)
{
  return 1;
}

/* return true, if the passed argument is understood */
int cpu_args(char *p)
{
  /* no args */
  return 0;
}

/* parse cpu-specific directives; return pointer to end of
   cpu-specific text */
char *parse_cpu_special(char *s)
{
  /* no specials */
  return s;
}
