/*  src/m68kpsp.c: Q68 emulator interface
    Copyright 2009 Andrew Church

    This file is part of Yabause.

    Yabause 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 of the License, or
    (at your option) any later version.

    Yabause 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 Yabause; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include "yabause.h"
#include "m68kcore.h"

#include "q68/q68.h"

/*************************************************************************/

/**
 * NEED_TRAMPOLINE:  Defined on platforms where we need trampoline
 * functions to convert read/write calls from the native format to the
 * FASTCALL format used by Yabause.
 */
#ifdef CPU_X86
# define NEED_TRAMPOLINE
#endif

/**
 * PROFILE_68K: Perform simple profiling of the 68000 emulation, reporting
 * the average time per 68000 clock cycle.  (Realtime execution would be
 * around 88.5 nsec/cycle.)
 *
 * Note that this profiling has an overhead of two YabauseGetTicks() calls
 * for each M68K->Exec() call; on PSP, this amounts to about 3.8 usec per
 * Exec(), or 52.8 nsec/cycle at 72 cycles/call.
 */
// #define PROFILE_68K

// #define COUNT_OPCODES  // see COUNT_OPCODES in q68-core.c

/*************************************************************************/

/* Interface function declarations (must come before interface definition) */

static int m68kq68_init(void);
static void m68kq68_deinit(void);
static void m68kq68_reset(void);

static FASTCALL s32 m68kq68_exec(s32 cycles);
static void m68kq68_sync(void);

static u32 m68kq68_get_dreg(u32 num);
static u32 m68kq68_get_areg(u32 num);
static u32 m68kq68_get_pc(void);
static u32 m68kq68_get_sr(void);
static u32 m68kq68_get_usp(void);
static u32 m68kq68_get_ssp(void);

static void m68kq68_set_dreg(u32 num, u32 val);
static void m68kq68_set_areg(u32 num, u32 val);
static void m68kq68_set_pc(u32 val);
static void m68kq68_set_sr(u32 val);
static void m68kq68_set_usp(u32 val);
static void m68kq68_set_ssp(u32 val);

static FASTCALL void m68kq68_set_irq(s32 level);
static FASTCALL void m68kq68_write_notify(u32 address, u32 size);

static void m68kq68_set_fetch(u32 low_addr, u32 high_addr, pointer fetch_addr);
static void m68kq68_set_readb(M68K_READ *func);
static void m68kq68_set_readw(M68K_READ *func);
static void m68kq68_set_writeb(M68K_WRITE *func);
static void m68kq68_set_writew(M68K_WRITE *func);

static uint32_t dummy_read(uint32_t address);
static void dummy_write(uint32_t address, uint32_t data);

#ifdef NEED_TRAMPOLINE
static uint32_t readb_trampoline(uint32_t address);
static uint32_t readw_trampoline(uint32_t address);
static void writeb_trampoline(uint32_t address, uint32_t data);
static void writew_trampoline(uint32_t address, uint32_t data);
#endif

static void m68kq68_save_state(FILE * fp);
static void m68kq68_load_state(FILE * fp);

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

/* Module interface definition */

M68K_struct M68KQ68 = {
    .id          = M68KCORE_Q68,
    .Name        = "Q68 68k Emulator Interface",

    .Init        = m68kq68_init,
    .DeInit      = m68kq68_deinit,
    .Reset       = m68kq68_reset,

    .Exec        = m68kq68_exec,
    .Sync        = m68kq68_sync,

    .GetDReg     = m68kq68_get_dreg,
    .GetAReg     = m68kq68_get_areg,
    .GetPC       = m68kq68_get_pc,
    .GetSR       = m68kq68_get_sr,
    .GetUSP      = m68kq68_get_usp,
    .GetMSP      = m68kq68_get_ssp,

    .SetDReg     = m68kq68_set_dreg,
    .SetAReg     = m68kq68_set_areg,
    .SetPC       = m68kq68_set_pc,
    .SetSR       = m68kq68_set_sr,
    .SetUSP      = m68kq68_set_usp,
    .SetMSP      = m68kq68_set_ssp,

    .SetIRQ      = m68kq68_set_irq,
    .WriteNotify = m68kq68_write_notify,

    .SetFetch    = m68kq68_set_fetch,
    .SetReadB    = m68kq68_set_readb,
    .SetReadW    = m68kq68_set_readw,
    .SetWriteB   = m68kq68_set_writeb,
    .SetWriteW   = m68kq68_set_writew,
    .SaveState   = m68kq68_save_state,
    .LoadState   = m68kq68_load_state,
};

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

/* Virtual processor state block */

static Q68State *state;


#ifdef NEED_TRAMPOLINE

/* Read/write functions passed to Set{Read,Write}[BW], called via the
 * trampolines */
static M68K_READ *real_readb, *real_readw;
static M68K_WRITE *real_writeb, *real_writew;

#endif

/*************************************************************************/
/************************** Interface functions **************************/
/*************************************************************************/

/**
 * m68kq68_init:  Initialize the virtual processpr.
 *
 * [Parameters]
 *     None
 * [Return value]
 *     Zero on success, negative on failure
 */
static int m68kq68_init(void)
{
    if (!(state = q68_create())) {
        return -1;
    }
    q68_set_irq(state, 0);
    q68_set_readb_func(state, dummy_read);
    q68_set_readw_func(state, dummy_read);
    q68_set_writeb_func(state, dummy_write);
    q68_set_writew_func(state, dummy_write);

    return 0;
}

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

/**
 * m68kq68_deinit:  Destroy the virtual processor.
 *
 * [Parameters]
 *     None
 * [Return value]
 *     None
 */
static void m68kq68_deinit(void)
{
    q68_destroy(state);
    state = NULL;
}

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

/**
 * m68kq68_reset:  Reset the virtual processor.
 *
 * [Parameters]
 *     None
 * [Return value]
 *     None
 */
static void m68kq68_reset(void)
{
    q68_reset(state);
}

/*************************************************************************/

/**
 * m68kq68_exec:  Execute instructions for the given number of clock cycles.
 *
 * [Parameters]
 *     cycles: Number of clock cycles to execute
 * [Return value]
 *     Number of clock cycles actually executed
 */
static FASTCALL s32 m68kq68_exec(s32 cycles)
{
#ifdef PROFILE_68K
    static uint32_t tot_cycles = 0, tot_usec = 0, tot_ticks = 0;
    static uint32_t last_report = 0;
    uint32_t start, end;
    start = (uint32_t) YabauseGetTicks();
    int retval = q68_run(state, cycles);
    end = (uint32_t) YabauseGetTicks();
    tot_cycles += cycles;
    tot_ticks += end - start;
    if (tot_cycles/1000000 > last_report) {
        tot_usec += (uint64_t)tot_ticks * 1000000 / yabsys.tickfreq;
        tot_ticks = 0;
        fprintf(stderr, "%ld cycles in %.3f sec = %.3f nsec/cycle\n",
                (long)tot_cycles, (double)tot_usec/1000000,
                ((double)tot_usec / (double)tot_cycles) * 1000);
        last_report = tot_cycles/1000000;
# ifdef COUNT_OPCODES
        if (last_report % 100 == 0) {
            extern uint32_t q68_ops[128], q68_4xxx_ops[32];
            int i;
            fprintf(stderr, "Opcodes per 1M cycles:\n");
            for (i = 0; i < 128; i++) {
                fprintf(stderr, "%s%8ld%s", i%8==0 ? "    " : "",
                        (long)((q68_ops[i] + last_report/2) / last_report),
                        i%8==7 ? "\n" : "");
            }
            fprintf(stderr, "$4xxx opcodes per 1M cycles:\n");
            for (i = 0; i < 32; i++) {
                fprintf(stderr, "%s%8ld%s", i%8==0 ? "    " : "",
                        (long)((q68_4xxx_ops[i] + last_report/2) / last_report),
                        i%8==7 ? "\n" : "");
            }
        }
# endif  // COUNT_OPCODES
    }
    return retval;
#else  // !PROFILE_68K
    return q68_run(state, cycles);
#endif
}

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

/**
 * m68kq68_sync:  Wait for background execution to finish.
 *
 * [Parameters]
 *     None
 * [Return value]
 *     None
 */
static void m68kq68_sync(void)
{
    /* Nothing to do */
}

/*************************************************************************/

/**
 * m68kq68_get_{dreg,areg,pc,sr,usp,ssp}:  Return the current value of
 * the specified register.
 *
 * [Parameters]
 *     num: Register number (m68kq68_get_dreg(), m68kq68_get_areg() only)
 * [Return value]
 *     None
 */

static u32 m68kq68_get_dreg(u32 num)
{
    return q68_get_dreg(state, num);
}

static u32 m68kq68_get_areg(u32 num)
{
    return q68_get_areg(state, num);
}

static u32 m68kq68_get_pc(void)
{
    return q68_get_pc(state);
}

static u32 m68kq68_get_sr(void)
{
    return q68_get_sr(state);
}

static u32 m68kq68_get_usp(void)
{
    return q68_get_usp(state);
}

static u32 m68kq68_get_ssp(void)
{
    return q68_get_ssp(state);
}

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

/**
 * m68kq68_set_{dreg,areg,pc,sr,usp,ssp}:  Set the value of the specified
 * register.
 *
 * [Parameters]
 *     num: Register number (m68kq68_set_dreg(), m68kq68_set_areg() only)
 *     val: Value to set
 * [Return value]
 *     None
 */

static void m68kq68_set_dreg(u32 num, u32 val)
{
    q68_set_dreg(state, num, val);
}

static void m68kq68_set_areg(u32 num, u32 val)
{
    q68_set_areg(state, num, val);
}

static void m68kq68_set_pc(u32 val)
{
    q68_set_pc(state, val);
}

static void m68kq68_set_sr(u32 val)
{
    q68_set_sr(state, val);
}

static void m68kq68_set_usp(u32 val)
{
    q68_set_usp(state, val);
}

static void m68kq68_set_ssp(u32 val)
{
    q68_set_ssp(state, val);
}

/*************************************************************************/

/**
 * m68kq68_set_irq:  Deliver an interrupt to the processor.
 *
 * [Parameters]
 *     level: Interrupt level (0-7)
 * [Return value]
 *     None
 */
static FASTCALL void m68kq68_set_irq(s32 level)
{
    q68_set_irq(state, level);
}

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

/**
 * m68kq68_write_notify:  Inform the 68k emulator that the given address
 * range has been modified.
 *
 * [Parameters]
 *     address: 68000 address of modified data
 *        size: Size of modified data in bytes
 * [Return value]
 *     None
 */
static FASTCALL void m68kq68_write_notify(u32 address, u32 size)
{
    q68_touch_memory(state, address, size);
}

/*************************************************************************/

/**
 * m68kq68_set_fetch:  Set the instruction fetch pointer for a region of
 * memory.  Not used by Q68.
 *
 * [Parameters]
 *       low_addr: Low address of memory region to set
 *      high_addr: High address of memory region to set
 *     fetch_addr: Pointer to corresponding memory region (NULL to disable)
 * [Return value]
 *     None
 */
static void m68kq68_set_fetch(u32 low_addr, u32 high_addr, pointer fetch_addr)
{
}

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

/**
 * m68kq68_set_{readb,readw,writeb,writew}:  Set functions for reading or
 * writing bytes or words in memory.
 *
 * [Parameters]
 *     func: Function to set
 * [Return value]
 *     None
 */

static void m68kq68_set_readb(M68K_READ *func)
{
#ifdef NEED_TRAMPOLINE
    real_readb = func;
    q68_set_readb_func(state, readb_trampoline);
#else
    q68_set_readb_func(state, (Q68ReadFunc *)func);
#endif
}

static void m68kq68_set_readw(M68K_READ *func)
{
#ifdef NEED_TRAMPOLINE
    real_readw = func;
    q68_set_readw_func(state, readw_trampoline);
#else
    q68_set_readw_func(state, (Q68ReadFunc *)func);
#endif
}

static void m68kq68_set_writeb(M68K_WRITE *func)
{
#ifdef NEED_TRAMPOLINE
    real_writeb = func;
    q68_set_writeb_func(state, writeb_trampoline);
#else
    q68_set_writeb_func(state, (Q68WriteFunc *)func);
#endif
}

static void m68kq68_set_writew(M68K_WRITE *func)
{
#ifdef NEED_TRAMPOLINE
    real_writew = func;
    q68_set_writew_func(state, writew_trampoline);
#else
    q68_set_writew_func(state, (Q68WriteFunc *)func);
#endif
}

/*************************************************************************/

/**
 * dummy_read:  Default read function, always returning 0 for any address.
 *
 * [Parameters]
 *     address: Address to read from
 * [Return value]
 *     Value read (always zero)
 */
static uint32_t dummy_read(uint32_t address)
{
    return 0;
}

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

/**
 * dummy_write:  Default write function, ignoring all writes.
 *
 * [Parameters]
 *     address: Address to write to
 *        data: Value to write
 * [Return value]
 *     None
 */
static void dummy_write(uint32_t address, uint32_t data)
{
}

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

#ifdef NEED_TRAMPOLINE

/**
 * read[bw]_trampoline, write[bw]_trampoline:  Adjust calling conventions
 * between the M68k emulator and Yabause's M68k core.
 *
 * [Parameters]
 *     address: Address to read from
 *        data: Value to write (only for write_trampoline)
 * [Return value]
 *     Value read (only for read_trampoline)
 */

static uint32_t readb_trampoline(uint32_t address) {
    return (*real_readb)(address);
}

static uint32_t readw_trampoline(uint32_t address) {
    return (*real_readw)(address);
}

static void writeb_trampoline(uint32_t address, uint32_t data) {
    return (*real_writeb)(address, data);
}

static void writew_trampoline(uint32_t address, uint32_t data) {
    return (*real_writew)(address, data);
}

#endif  // NEED_TRAMPOLINE

static void m68kq68_save_state(FILE * fp)
{
   int i = 0;
   u32 val = 0;
   IOCheck_struct check = { 0, 0 };

   for (i = 0; i < 8; i++)
   {
      val = q68_get_dreg(state, i);
      ywrite(&check, (void *)&val, sizeof(u32), 1, fp);
   }

   for (i = 0; i < 8; i++)
   {
      val = q68_get_areg(state, i, val);
      ywrite(&check, (void *)&val, sizeof(u32), 1, fp);
   }

   val = q68_get_pc(state, val);
   ywrite(&check, (void *)&val, sizeof(u32), 1, fp);

   val = q68_get_sr(state, val);
   ywrite(&check, (void *)&val, sizeof(u32), 1, fp);

   val = q68_get_usp(state, val);
   ywrite(&check, (void *)&val, sizeof(u32), 1, fp);

   val = q68_get_ssp(state, val);
   ywrite(&check, (void *)&val, sizeof(u32), 1, fp);
}

static void m68kq68_load_state(FILE * fp)
{
   int i = 0;
   u32 val = 0;
   IOCheck_struct check = { 0, 0 };

   for (i = 0; i < 8; i++)
   {
      yread(&check, (void *)&val, sizeof(u32), 1, fp);
      q68_set_dreg(state, i, val);
   }

   for (i = 0; i < 8; i++)
   {
      yread(&check, (void *)&val, sizeof(u32), 1, fp);
      q68_set_areg(state, i, val);
   }

   yread(&check, (void *)&val, sizeof(u32), 1, fp);
   q68_set_pc(state, val);

   yread(&check, (void *)&val, sizeof(u32), 1, fp);
   q68_set_sr(state, val);

   yread(&check, (void *)&val, sizeof(u32), 1, fp);
   q68_set_usp(state, val);

   yread(&check, (void *)&val, sizeof(u32), 1, fp);
   q68_set_ssp(state, val);
}

/*************************************************************************/
/*************************************************************************/

/*
 * Local variables:
 *   c-file-style: "stroustrup"
 *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
 *   indent-tabs-mode: nil
 * End:
 *
 * vim: expandtab shiftwidth=4:
 */
