/*
cycle.c

Copyright (c) 2015 Erik Gavriluk
Dual-licensed BSD 3-CLAUSE and GPL as per below.
Thanks to John Kowalksi for cycle count verification.

This file is part of LWTOOLS.

LWTOOLS 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 3 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, see <http://www.gnu.org/licenses/>.
*/

// Extracted from lwtools by gtoal@gtoal.com for use in a static binary
// translator (6809 code to C).  See https://www.gtoal.com/SBTPROJECT/6809sbt/

#include "cycle.h"

typedef enum { // flag bits have to be OR-ed together like a Pascal set.
  CYCLE_RUNTIME_ADJUSTMENT_NEEDED = 1,
  CYCLE_INDEXED = 2,
  CYCLE_RLIST = 4,
  CYCLE_LONG_BRANCH = 8
} cycle_flags;

typedef struct {
  int opc;
  int cycles_6809;
  cycle_flags flags;
} cycletable_t;

static const cycletable_t cycletable[] = {
  { 0x3a, 3, 0 },          // ABX

  { 0x89, 2, 0 },          // ADCA
  { 0x99, 4, 0 },
  { 0xa9, 4, CYCLE_INDEXED },
  { 0xb9, 5, 0 },

  { 0xc9, 2, 0 },          // ADCB
  { 0xd9, 4, 0 },
  { 0xe9, 4, CYCLE_INDEXED },
  { 0xf9, 5, 0 },

  { 0x8b, 2, 0 },          // ADDA
  { 0x9b, 4, 0 },
  { 0xab, 4, CYCLE_INDEXED },
  { 0xbb, 5, 0 },

  { 0xcb, 2, 0 },          // ADDB
  { 0xdb, 4, 0 },
  { 0xeb, 4, CYCLE_INDEXED },
  { 0xfb, 5, 0 },

  { 0xc3, 4, 0 },          // ADDD
  { 0xd3, 6, 0 },
  { 0xe3, 6, CYCLE_INDEXED },
  { 0xf3, 7, 0 },

  { 0x84, 2, 0 },          // ANDA
  { 0x94, 4, 0 },
  { 0xa4, 4, CYCLE_INDEXED },
  { 0xb4, 5, 0 },

  { 0xc4, 2, 0 },          // ANDB
  { 0xd4, 4, 0 },
  { 0xe4, 4, CYCLE_INDEXED },
  { 0xf4, 5, 0 },

  { 0x1c, 3, 0 },          // ANDCC

  { 0x48, 2, 0 },          // ASL
  { 0x58, 2, 0 },
  { 0x08, 6, 0 },
  { 0x68, 6, CYCLE_INDEXED },
  { 0x78, 7, 0 },

  { 0x47, 2, 0 },          // ASR
  { 0x57, 2, 0 },
  { 0x07, 6, 0 },
  { 0x67, 6, CYCLE_INDEXED },
  { 0x77, 7, 0 },

  { 0x85, 2, 0 },          // BITA
  { 0x95, 4, 0 },
  { 0xa5, 4, CYCLE_INDEXED },
  { 0xb5, 5, 0 },

  { 0xc5, 2, 0 },          // BITB
  { 0xd5, 4, 0 },
  { 0xe5, 4, CYCLE_INDEXED },
  { 0xf5, 5, 0 },

  { 0x20, 3, 0 },          // BRA
  { 0x21, 3, 0 },
  { 0x22, 3, 0 },
  { 0x23, 3, 0 },
  { 0x24, 3, 0 },
  { 0x25, 3, 0 },
  { 0x26, 3, 0 },
  { 0x27, 3, 0 },
  { 0x28, 3, 0 },
  { 0x29, 3, 0 },
  { 0x2a, 3, 0 },
  { 0x2b, 3, 0 },
  { 0x2c, 3, 0 },
  { 0x2d, 3, 0 },
  { 0x2e, 3, 0 },
  { 0x2f, 3, 0 },

  { 0x8d, 7, 0 },          // BSR

  { 0x4f, 2, 0 },          // CLR
  { 0x5f, 2, 0 },
  { 0x0f, 6, 0 },
  { 0x6f, 6, CYCLE_INDEXED },
  { 0x7f, 7, 0 },

  { 0x81, 2, 0 },          // CMPA
  { 0x91, 4, 0 },
  { 0xa1, 4, CYCLE_INDEXED },
  { 0xb1, 5, 0 },

  { 0xc1, 2, 0 },          // CMPB
  { 0xd1, 4, 0 },
  { 0xe1, 4, CYCLE_INDEXED },
  { 0xf1, 5, 0 },

  { 0x1083, 5, 0 },        // CMPD
  { 0x1093, 7, 0 },
  { 0x10a3, 7, CYCLE_INDEXED },
  { 0x10b3, 8, 0 },

  { 0x118c, 5, 0 },        // CMPS
  { 0x119c, 7, 0 },
  { 0x11ac, 7, CYCLE_INDEXED },
  { 0x11bc, 8, 0 },

  { 0x1183, 5, 0 },        // CMPU
  { 0x1193, 7, 0 },
  { 0x11a3, 7, CYCLE_INDEXED },
  { 0x11b3, 8, 0 },

  { 0x8c, 4, 0 },          // CMPX
  { 0x9c, 6, 0 },
  { 0xac, 6, CYCLE_INDEXED },
  { 0xbc, 7, 0 },

  { 0x108c, 5, 0 },        // CMPY
  { 0x109c, 7, 0 },
  { 0x10ac, 7, CYCLE_INDEXED },
  { 0x10bc, 8, 0 },

  { 0x43, 2, 0 },          // COMA
  { 0x53, 2, 0 },          // COMB

  { 0x03, 6, 0 },          // COM
  { 0x63, 6, CYCLE_INDEXED },
  { 0x73, 7, 0 },

  { 0x3c, 22, CYCLE_RUNTIME_ADJUSTMENT_NEEDED },  // CWAI

  { 0x19, 2, 0 },          // DAA

  { 0x4a, 2, 0 },          // DECA
  { 0x5a, 2, 0 },          // DECB

  { 0x0a, 6, 0 },          // DEC
  { 0x6a, 6, CYCLE_INDEXED },
  { 0x7a, 7, 0 },

  { 0x88, 2, 0 },          // EORA
  { 0x98, 4, 0 },
  { 0xa8, 4, CYCLE_INDEXED },
  { 0xb8, 5, 0 },

  { 0xc8, 2, 0 },          // EORB
  { 0xd8, 4, 0 },
  { 0xe8, 4, CYCLE_INDEXED },
  { 0xf8, 5, 0 },

  { 0x1e, 8, 0 },          // EXG

  { 0x4c, 2, 0 },          // INCA
  { 0x5c, 2, 0 },          // INCB

  { 0x0c, 6, 0 },          // INC
  { 0x6c, 6, CYCLE_INDEXED },
  { 0x7c, 7, 0 },

  { 0x0e, 3, 0 },          // JMP
  { 0x6e, 3, CYCLE_INDEXED },
  { 0x7e, 4, 0 },

  { 0x9d, 7, 0 },          // JSR
  { 0xad, 7, CYCLE_INDEXED },
  { 0xbd, 8, 0 },

  { 0x16, 5, 0 },          // LBRA
  { 0x17, 9, 0 },          // LBSR
  { 0x1021, 5, 0 },        // LBRN
  { 0x1022, 5, CYCLE_LONG_BRANCH },        //    conditional long branches are all 5 cycles, +1 cycle if the branch is taken
  { 0x1023, 5, CYCLE_LONG_BRANCH },
  { 0x1024, 5, CYCLE_LONG_BRANCH },
  { 0x1025, 5, CYCLE_LONG_BRANCH },
  { 0x1026, 5, CYCLE_LONG_BRANCH },
  { 0x1027, 5, CYCLE_LONG_BRANCH },
  { 0x1028, 5, CYCLE_LONG_BRANCH },    
  { 0x1029, 5, CYCLE_LONG_BRANCH },    
  { 0x102a, 5, CYCLE_LONG_BRANCH },
  { 0x102b, 5, CYCLE_LONG_BRANCH },
  { 0x102c, 5, CYCLE_LONG_BRANCH },
  { 0x102d, 5, CYCLE_LONG_BRANCH },
  { 0x102e, 5, CYCLE_LONG_BRANCH },
  { 0x102f, 5, CYCLE_LONG_BRANCH },

  { 0x86, 2, 0 },          // LDA
  { 0x96, 4, 0 },
  { 0xa6, 4, CYCLE_INDEXED },
  { 0xb6, 5, 0 },

  { 0xc6, 2, 0 },          // LDB
  { 0xd6, 4, 0 },                     // check this one against pre-edited source
  { 0xe6, 4, CYCLE_INDEXED },
  { 0xf6, 5, 0 },

  { 0xcc, 3, 0 },          // LDD
  { 0xdc, 5, 0 },
  { 0xec, 5, CYCLE_INDEXED },
  { 0xfc, 6, 0 },

  { 0x10ce, 4, 0 },        // LDS
  { 0x10de, 6, 0 },
  { 0x10ee, 6, CYCLE_INDEXED },
  { 0x10fe, 7, 0 },

  { 0xce, 3, 0 },          // LDU
  { 0xde, 5, 0 },
  { 0xee, 5, CYCLE_INDEXED },
  { 0xfe, 6, 0 },

  { 0x8e, 3, 0 },          // LDX
  { 0x9e, 5, 0 },
  { 0xae, 5, CYCLE_INDEXED },
  { 0xbe, 6, 0 },

  { 0x108e, 4, 0 },        // LDY
  { 0x109e, 6, 0 },
  { 0x10ae, 6, CYCLE_INDEXED },
  { 0x10be, 7, 0 },

  { 0x32, 4, CYCLE_INDEXED },
  { 0x33, 4, CYCLE_INDEXED },
  { 0x30, 4, CYCLE_INDEXED },  // LEA
  { 0x31, 4, CYCLE_INDEXED },

  { 0x44, 2, 0 },          // LSRA
  { 0x54, 2, 0 },          // LSRB

  { 0x04, 6, 0 },          // LSR
  { 0x64, 6, CYCLE_INDEXED },
  { 0x74, 7, 0 },

  { 0x3d, 11, 0 },        // MUL

  { 0x40, 2, 0 },          // NEGA
  { 0x50, 2, 0 },          // NEGB

  { 0x00, 6, 0 },          // NEG
  { 0x60, 6, CYCLE_INDEXED },
  { 0x70, 7, 0 },

  { 0x12, 2, 0 },          // NOP

  { 0x8a, 2, 0 },          // ORA
  { 0x9a, 4, 0 },
  { 0xaa, 4, CYCLE_INDEXED },
  { 0xba, 5, 0 },

  { 0xca, 2, 0 },          // ORB
  { 0xda, 4, 0 },
  { 0xea, 4, CYCLE_INDEXED },
  { 0xfa, 5, 0 },

  { 0x1a, 3, 0 },          // ORCC

  { 0x34, 5, CYCLE_RLIST },      // PSHS
  { 0x36, 5, CYCLE_RLIST },      // PSHU
  { 0x35, 5, CYCLE_RLIST },      // PULS
  { 0x37, 5, CYCLE_RLIST },      // PULU

  { 0x49, 2, 0 },          // ROLA
  { 0x59, 2, 0 },          // ROLB

  { 0x09, 6, 0 },          // ROL
  { 0x69, 6, CYCLE_INDEXED },
  { 0x79, 7, 0 },

  { 0x46, 2, 0 },          // RORA
  { 0x56, 2, 0 },          // RORB

  { 0x06, 6, 0 },          // ROR
  { 0x66, 6, CYCLE_INDEXED },
  { 0x76, 7, 0 },

  { 0x3b, 6, CYCLE_RUNTIME_ADJUSTMENT_NEEDED },  // RTI

  { 0x39, 5, 0 },          // RTS

  { 0x82, 2, 0 },          // SBCA
  { 0x92, 4, 0 },
  { 0xa2, 4, CYCLE_INDEXED },
  { 0xb2, 5, 0 },

  { 0xc2, 2, 0 },          // SBCB
  { 0xd2, 4, 0 },
  { 0xe2, 4, CYCLE_INDEXED },
  { 0xf2, 5, 0 },

  { 0x1d, 2, 0 },          // SEX

  { 0x97, 4, 0 },          // STA
  { 0xa7, 4, CYCLE_INDEXED },
  { 0xb7, 5, 0 },

  { 0xd7, 4, 0 },          // STB
  { 0xe7, 4, CYCLE_INDEXED },
  { 0xf7, 5, 0 },

  { 0xdd, 5, 0 },          // STD
  { 0xed, 5, CYCLE_INDEXED },
  { 0xfd, 6, 0 },

  { 0x10df, 6, 0 },        // STS
  { 0x10ef, 6, CYCLE_INDEXED },
  { 0x10ff, 7, 0 },

  { 0xdf, 5, 0 },          // STU
  { 0xef, 5, CYCLE_INDEXED },
  { 0xff, 6, 0 },

  { 0x9f, 5, 0 },          // STX
  { 0xaf, 5, CYCLE_INDEXED },
  { 0xbf, 6, 0 },

  { 0x109f, 6, 0 },        // STY
  { 0x10af, 6, CYCLE_INDEXED },
  { 0x10bf, 7, 0 },

  { 0x80, 2, 0 },          // SUBA
  { 0x90, 4, 0 },
  { 0xa0, 4, CYCLE_INDEXED },
  { 0xb0, 5, 0 },

  { 0xc0, 2, 0 },          // SUBB
  { 0xd0, 4, 0 },
  { 0xe0, 4, CYCLE_INDEXED },
  { 0xf0, 5, 0 },

  { 0x83, 4, 0 },                  // SUBD
  { 0x93, 6, 0 },
  { 0xa3, 6, CYCLE_INDEXED },
  { 0xb3, 7, 0 },

  { 0x3f, 19, 0 },                // SWI
  { 0x103f, 20, 0 },              // SWI2
  { 0x113f, 20, 0 },              // SWI3

  { 0x13, 2, CYCLE_RUNTIME_ADJUSTMENT_NEEDED },    // SYNC

  { 0x1f, 6, 0 },                  // TFR

  { 0x4d, 2, 0 },                  // TSTA
  { 0x5d, 2, 0 },                  // TSTB

  { 0x0d, 6, 0 },                  // TST
  { 0x6d, 6, CYCLE_INDEXED },
  { 0x7d, 7, 0 },

  { -1, -1, -1 }
};

typedef struct
{
  int cycles_6809_indexed;
  int cycles_6809_indirect;
} indtab_t;

static indtab_t indtab[] = {
    { 2, -1 },   // 0000    ,R+
    { 3, 6 },    // 0001    ,R++ [,R++]
    { 2, -1 },   // 0010    ,-R
    { 3, 6 },    // 0011    ,--R [,--R]
    { 0, 3 },    // 0100    ,R   [,R]
    { 1, 4 },    // 0101   B,R   [B,R]
    { 1, 4 },    // 0110   A,R   [A,R]
    { 1, 1 },    // 0111   E,R   [E,R]
    { 1, 4 },    // 1000   n,R   [n,R]
    { 4, 7 },    // 1001   n,R   [n,R]   (16 bit)
    { 1, 1 },    // 1010   F,R   [F,R]
    { 4, 4 },    // 1011   D,R   [D,R]
    { 1, 4 },    // 1100   n,PC  [n,PC]
    { 5, 8 },    // 1101   n,PC  [n,PC]  (16 bit)
    { 4, 4 },    // 1110   W,R   [W,R]
    { -1, 5 },   // 1111         [n]     (16 bit)
};

static int base_cycle_count(int opc, int *flags) {
  int i = 0;
  for (;;) {
    if (cycletable[i].opc == -1) return 0;
    if (cycletable[i].opc == opc) {
      *flags = cycletable[i].flags;
      // Now preloaded in the table:
      // *flags |= (((opc >= 0x1022) && (opc <= 0x102f)) ? CYCLE_LONG_BRANCH : 0);
      return cycletable[i].cycles_6809;
    }
    i += 1;
  }
}

/* calculates additional ticks from post byte in both indexed and indirect modes */
static int indirect_cycle_adjustment(int postbyte) {
  if ((postbyte & 0x80) == 0) return 1; /* 5 bit offset */
  if (postbyte & 0x10) return indtab[postbyte & 0xf].cycles_6809_indirect;
  return indtab[postbyte & 0xf].cycles_6809_indexed;
}

/* calculate additional ticks from post byte in rlist (PSHS A,B,X...) */
static int rlist_cycle_adjustment(int postbyte) {
  int i, cycles = 0;
  for (i = 0; i < 8; i++) {
    if (postbyte & (1 << i)) cycles += (i <= 3) ? 1 : 2;  // 1 cycle for 8 bit regs CC-DP (bits 0-3), otherwise 2
  }
  return cycles;
}

// This is to calculate the cycle count for an instruction at
// translate time, to reduce the run-time overhead on the
// static binary translator (and also simplify its code).

int cycle_count(uint8_t *mem, CODE_ADDR addr, int branch_taken_cycle_adjustment /* 0 or 1 */) {
  int opc, postbyte, cycle_adj = 0, cycle_base = 0, cycle_flags = 0;

  // NOTE: for the static binary translator, a few opcodes (RTI comes to mind)
  // cannot have their cycle counts determined statically, and will have to
  // be adjusted at runtime as a special case. For those, we'll assign the
  // base value statically and just add the per-register overhead at runtime.

  // However for the branch/don't branch opcodes, we can actually add the
  // cycle count for each branch separately, so no run time adjustment needed,
  // just two calls at translate time.

  if (((opc = mem[addr++])>>1) == 0x08) opc = (opc << 8) | mem[addr++]; // opc==0x10 || opc==0x11

  postbyte = mem[addr++]; // post byte, for opcodes such as PSHS
                          // addr is left pointing at the operand if present,
                          // or the next instruction if not.

  if ((cycle_base = base_cycle_count(opc, &cycle_flags)) != 0) {
    cycle_adj = (cycle_flags & CYCLE_LONG_BRANCH ? branch_taken_cycle_adjustment       : 0)
              + (cycle_flags & CYCLE_INDEXED     ? indirect_cycle_adjustment(postbyte) : 0)
              + (cycle_flags & CYCLE_RLIST       ? rlist_cycle_adjustment(postbyte)    : 0);
    // fprintf(stderr, "%d+%d\n", cycle_base, cycle_adj); // debugging for now
    return cycle_base + cycle_adj;
  }
  // Illegal instruction?
  return 0;
}
