mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-26 19:50:45 +00:00
718112a8fe
Currently none of these is being used, but eventually they will, once more code gets ported over. So it's better to have them right away and avoid editing the project file too much, only to revert that later.
549 lines
16 KiB
C++
549 lines
16 KiB
C++
// Core SPC emulation: CPU, timers, SMP registers, memory
|
|
|
|
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
|
|
|
|
#include "Snes_Spc.h"
|
|
|
|
#include <string.h>
|
|
|
|
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
|
General Public License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version. This
|
|
module 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 Lesser General Public License for more
|
|
details. You should have received a copy of the GNU Lesser General Public
|
|
License along with this module; if not, write to the Free Software Foundation,
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "blargg_source.h"
|
|
|
|
#define RAM (m.ram.ram)
|
|
#define REGS (m.smp_regs [0])
|
|
#define REGS_IN (m.smp_regs [1])
|
|
|
|
// (n ? n : 256)
|
|
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
|
|
|
|
// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which
|
|
// do crazy echo buffer accesses.
|
|
#ifndef SPC_MORE_ACCURACY
|
|
#define SPC_MORE_ACCURACY 0
|
|
#endif
|
|
|
|
#ifdef BLARGG_ENABLE_OPTIMIZER
|
|
#include BLARGG_ENABLE_OPTIMIZER
|
|
#endif
|
|
|
|
|
|
//// Timers
|
|
|
|
#if SPC_DISABLE_TEMPO
|
|
#define TIMER_DIV( t, n ) ((n) >> t->prescaler)
|
|
#define TIMER_MUL( t, n ) ((n) << t->prescaler)
|
|
#else
|
|
#define TIMER_DIV( t, n ) ((n) / t->prescaler)
|
|
#define TIMER_MUL( t, n ) ((n) * t->prescaler)
|
|
#endif
|
|
|
|
Snes_Spc::Timer* Snes_Spc::run_timer_( Timer* t, rel_time_t time )
|
|
{
|
|
int elapsed = TIMER_DIV( t, time - t->next_time ) + 1;
|
|
t->next_time += TIMER_MUL( t, elapsed );
|
|
|
|
if ( t->enabled )
|
|
{
|
|
int remain = IF_0_THEN_256( t->period - t->divider );
|
|
int divider = t->divider + elapsed;
|
|
int over = elapsed - remain;
|
|
if ( over >= 0 )
|
|
{
|
|
int n = over / t->period;
|
|
t->counter = (t->counter + 1 + n) & 0x0F;
|
|
divider = over - n * t->period;
|
|
}
|
|
t->divider = (uint8_t) divider;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
inline Snes_Spc::Timer* Snes_Spc::run_timer( Timer* t, rel_time_t time )
|
|
{
|
|
if ( time >= t->next_time )
|
|
t = run_timer_( t, time );
|
|
return t;
|
|
}
|
|
|
|
|
|
//// ROM
|
|
|
|
void Snes_Spc::enable_rom( int enable )
|
|
{
|
|
if ( m.rom_enabled != enable )
|
|
{
|
|
m.rom_enabled = enable;
|
|
if ( enable )
|
|
memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram );
|
|
memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size );
|
|
// TODO: ROM can still get overwritten when DSP writes to echo buffer
|
|
}
|
|
}
|
|
|
|
|
|
//// DSP
|
|
|
|
#if SPC_LESS_ACCURATE
|
|
int const max_reg_time = 29;
|
|
|
|
signed char const Snes_Spc::reg_times_ [256] =
|
|
{
|
|
-1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22,
|
|
2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23,
|
|
5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23,
|
|
8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24,
|
|
11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24,
|
|
14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24,
|
|
17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25,
|
|
20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25,
|
|
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
|
};
|
|
|
|
#define RUN_DSP( time, offset ) \
|
|
int count = (time) - (offset) - m.dsp_time;\
|
|
if ( count >= 0 )\
|
|
{\
|
|
int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\
|
|
m.dsp_time += clock_count;\
|
|
dsp.run( clock_count );\
|
|
}
|
|
#else
|
|
#define RUN_DSP( time, offset ) \
|
|
{\
|
|
int count = (time) - m.dsp_time;\
|
|
if ( !SPC_MORE_ACCURACY || count )\
|
|
{\
|
|
assert( count > 0 );\
|
|
m.dsp_time = (time);\
|
|
dsp.run( count );\
|
|
}\
|
|
}
|
|
#endif
|
|
|
|
int Snes_Spc::dsp_read( rel_time_t time )
|
|
{
|
|
RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] );
|
|
|
|
int result = dsp.read( REGS [r_dspaddr] & 0x7F );
|
|
|
|
#ifdef SPC_DSP_READ_HOOK
|
|
SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result );
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
inline void Snes_Spc::dsp_write( int data, rel_time_t time )
|
|
{
|
|
RUN_DSP( time, reg_times [REGS [r_dspaddr]] )
|
|
#if SPC_LESS_ACCURATE
|
|
else if ( m.dsp_time == skipping_time )
|
|
{
|
|
int r = REGS [r_dspaddr];
|
|
if ( r == Spc_Dsp::r_kon )
|
|
m.skipped_kon |= data & ~dsp.read( Spc_Dsp::r_koff );
|
|
|
|
if ( r == Spc_Dsp::r_koff )
|
|
{
|
|
m.skipped_koff |= data;
|
|
m.skipped_kon &= ~data;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SPC_DSP_WRITE_HOOK
|
|
SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data );
|
|
#endif
|
|
|
|
if ( REGS [r_dspaddr] <= 0x7F )
|
|
dsp.write( REGS [r_dspaddr], data );
|
|
else if ( !SPC_MORE_ACCURACY )
|
|
debug_printf( "SPC wrote to DSP register > $7F\n" );
|
|
}
|
|
|
|
|
|
//// Memory access extras
|
|
|
|
#if SPC_MORE_ACCURACY
|
|
#define MEM_ACCESS( time, addr ) \
|
|
{\
|
|
if ( time >= m.dsp_time )\
|
|
{\
|
|
RUN_DSP( time, max_reg_time );\
|
|
}\
|
|
}
|
|
#elif !defined (NDEBUG)
|
|
// Debug-only check for read/write within echo buffer, since this might result in
|
|
// inaccurate emulation due to the DSP not being caught up to the present.
|
|
|
|
bool Snes_Spc::check_echo_access( int addr )
|
|
{
|
|
if ( !(dsp.read( Spc_Dsp::r_flg ) & 0x20) )
|
|
{
|
|
int start = 0x100 * dsp.read( Spc_Dsp::r_esa );
|
|
int size = 0x800 * (dsp.read( Spc_Dsp::r_edl ) & 0x0F);
|
|
int end = start + (size ? size : 4);
|
|
if ( start <= addr && addr < end )
|
|
{
|
|
if ( !m.echo_accessed )
|
|
{
|
|
m.echo_accessed = 1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) );
|
|
#else
|
|
#define MEM_ACCESS( time, addr )
|
|
#endif
|
|
|
|
|
|
//// CPU write
|
|
|
|
#if SPC_MORE_ACCURACY
|
|
static unsigned char const glitch_probs [3] [256] =
|
|
{
|
|
0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B,
|
|
0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08,
|
|
0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09,
|
|
0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01,
|
|
0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05,
|
|
0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07,
|
|
0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07,
|
|
0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01,
|
|
0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09,
|
|
0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08,
|
|
0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03,
|
|
0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03,
|
|
0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07,
|
|
0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02,
|
|
0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02,
|
|
0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01,
|
|
|
|
0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07,
|
|
0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06,
|
|
0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09,
|
|
0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03,
|
|
0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07,
|
|
0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03,
|
|
0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06,
|
|
0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03,
|
|
0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05,
|
|
0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04,
|
|
0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05,
|
|
0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01,
|
|
0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05,
|
|
0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01,
|
|
0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03,
|
|
0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01,
|
|
|
|
0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A,
|
|
0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A,
|
|
0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A,
|
|
0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09,
|
|
0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09,
|
|
0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02,
|
|
0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07,
|
|
0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04,
|
|
0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A,
|
|
0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07,
|
|
0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04,
|
|
0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02,
|
|
0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06,
|
|
0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03,
|
|
0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02,
|
|
0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03,
|
|
};
|
|
#endif
|
|
|
|
// Read/write handlers are divided into multiple functions to keep rarely-used
|
|
// functionality separate so often-used functionality can be optimized better
|
|
// by compiler.
|
|
|
|
// If write isn't preceded by read, data has this added to it
|
|
int const no_read_before_write = 0x2000;
|
|
|
|
void Snes_Spc::cpu_write_smp_reg_( int data, rel_time_t time, uint16_t addr )
|
|
{
|
|
switch ( addr )
|
|
{
|
|
case r_t0target:
|
|
case r_t1target:
|
|
case r_t2target: {
|
|
Timer* t = &m.timers [addr - r_t0target];
|
|
int period = IF_0_THEN_256( data );
|
|
if ( t->period != period )
|
|
{
|
|
t = run_timer( t, time );
|
|
#if SPC_MORE_ACCURACY
|
|
// Insane behavior when target is written just after counter is
|
|
// clocked and counter matches new period and new period isn't 1, 2, 4, or 8
|
|
if ( t->divider == (period & 0xFF) &&
|
|
t->next_time == time + TIMER_MUL( t, 1 ) &&
|
|
((period - 1) | ~0x0F) & period )
|
|
{
|
|
//debug_printf( "SPC pathological timer target write\n" );
|
|
|
|
// If the period is 3, 5, or 9, there's a probability this behavior won't occur,
|
|
// based on the previous period
|
|
int prob = 0xFF;
|
|
int old_period = t->period & 0xFF;
|
|
if ( period == 3 ) prob = glitch_probs [0] [old_period];
|
|
if ( period == 5 ) prob = glitch_probs [1] [old_period];
|
|
if ( period == 9 ) prob = glitch_probs [2] [old_period];
|
|
|
|
// The glitch suppresses incrementing of one of the counter bits, based on
|
|
// the lowest set bit in the new period
|
|
int b = 1;
|
|
while ( !(period & b) )
|
|
b <<= 1;
|
|
|
|
if ( (rand() >> 4 & 0xFF) <= prob )
|
|
t->divider = (t->divider - b) & 0xFF;
|
|
}
|
|
#endif
|
|
t->period = period;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case r_t0out:
|
|
case r_t1out:
|
|
case r_t2out:
|
|
if ( !SPC_MORE_ACCURACY )
|
|
debug_printf( "SPC wrote to counter %d\n", (int) addr - r_t0out );
|
|
|
|
if ( data < no_read_before_write / 2 )
|
|
run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0;
|
|
break;
|
|
|
|
// Registers that act like RAM
|
|
case 0x8:
|
|
case 0x9:
|
|
REGS_IN [addr] = (uint8_t) data;
|
|
break;
|
|
|
|
case r_test:
|
|
if ( (uint8_t) data != 0x0A )
|
|
debug_printf( "SPC wrote to test register\n" );
|
|
break;
|
|
|
|
case r_control:
|
|
// port clears
|
|
if ( data & 0x10 )
|
|
{
|
|
REGS_IN [r_cpuio0] = 0;
|
|
REGS_IN [r_cpuio1] = 0;
|
|
}
|
|
if ( data & 0x20 )
|
|
{
|
|
REGS_IN [r_cpuio2] = 0;
|
|
REGS_IN [r_cpuio3] = 0;
|
|
}
|
|
|
|
// timers
|
|
{
|
|
for ( int i = 0; i < timer_count; i++ )
|
|
{
|
|
Timer* t = &m.timers [i];
|
|
int enabled = data >> i & 1;
|
|
if ( t->enabled != enabled )
|
|
{
|
|
t = run_timer( t, time );
|
|
t->enabled = enabled;
|
|
if ( enabled )
|
|
{
|
|
t->divider = 0;
|
|
t->counter = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
enable_rom( data & 0x80 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Snes_Spc::cpu_write_smp_reg( int data, rel_time_t time, uint16_t addr )
|
|
{
|
|
if ( addr == r_dspdata ) // 99%
|
|
dsp_write( data, time );
|
|
else
|
|
cpu_write_smp_reg_( data, time, addr );
|
|
}
|
|
|
|
void Snes_Spc::cpu_write_high( int data, uint8_t i )
|
|
{
|
|
assert ( i < rom_size );
|
|
m.hi_ram [i] = (uint8_t) data;
|
|
if ( m.rom_enabled )
|
|
RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM
|
|
}
|
|
|
|
void Snes_Spc::cpu_write( int data, uint16_t addr, rel_time_t time )
|
|
{
|
|
MEM_ACCESS( time, addr )
|
|
|
|
// RAM
|
|
RAM [addr] = (uint8_t) data;
|
|
if ( addr >= 0xF0 ) // 64%
|
|
{
|
|
const uint16_t reg = addr - 0xF0;
|
|
// $F0-$FF
|
|
if ( reg < reg_count ) // 87%
|
|
{
|
|
REGS [reg] = (uint8_t) data;
|
|
|
|
// Ports
|
|
#ifdef SPC_PORT_WRITE_HOOK
|
|
if ( (unsigned) (reg - r_cpuio0) < port_count )
|
|
SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0),
|
|
(uint8_t) data, ®S [r_cpuio0] );
|
|
#endif
|
|
|
|
// Registers other than $F2 and $F4-$F7
|
|
if ( reg != 2 && (reg < 4 || reg > 7) ) // 36%
|
|
cpu_write_smp_reg( data, time, reg );
|
|
}
|
|
// High mem/address wrap-around
|
|
else if ( addr >= rom_addr ) // 1% in IPL ROM area or address wrapped around
|
|
cpu_write_high( data, addr - rom_addr );
|
|
}
|
|
}
|
|
|
|
|
|
//// CPU read
|
|
|
|
inline int Snes_Spc::cpu_read_smp_reg( int reg, rel_time_t time )
|
|
{
|
|
int result = REGS_IN [reg];
|
|
reg -= r_dspaddr;
|
|
// DSP addr and data
|
|
if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3
|
|
{
|
|
result = REGS [r_dspaddr];
|
|
if ( (unsigned) reg == 1 )
|
|
result = dsp_read( time ); // 0xF3
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int Snes_Spc::cpu_read( uint16_t addr, rel_time_t time )
|
|
{
|
|
MEM_ACCESS( time, addr )
|
|
|
|
// RAM
|
|
int result = RAM [addr];
|
|
int reg = addr - 0xF0;
|
|
if ( reg >= 0 ) // 40%
|
|
{
|
|
reg -= 0x10;
|
|
if ( (unsigned) reg >= 0xFF00 ) // 21%
|
|
{
|
|
reg += 0x10 - r_t0out;
|
|
|
|
// Timers
|
|
if ( (unsigned) reg < timer_count ) // 90%
|
|
{
|
|
Timer* t = &m.timers [reg];
|
|
if ( time >= t->next_time )
|
|
t = run_timer_( t, time );
|
|
result = t->counter;
|
|
t->counter = 0;
|
|
}
|
|
// Other registers
|
|
else if ( reg < 0 ) // 10%
|
|
{
|
|
result = cpu_read_smp_reg( reg + r_t0out, time );
|
|
}
|
|
else // 1%
|
|
{
|
|
assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 );
|
|
result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time );
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//// Run
|
|
|
|
// Prefix and suffix for CPU emulator function
|
|
#define SPC_CPU_RUN_FUNC \
|
|
uint8_t* Snes_Spc::run_until_( time_t end_time )\
|
|
{\
|
|
rel_time_t rel_time = m.spc_time - end_time;\
|
|
assert( rel_time <= 0 );\
|
|
m.spc_time = end_time;\
|
|
m.dsp_time += rel_time;\
|
|
m.timers [0].next_time += rel_time;\
|
|
m.timers [1].next_time += rel_time;\
|
|
m.timers [2].next_time += rel_time;
|
|
|
|
#define SPC_CPU_RUN_FUNC_END \
|
|
m.spc_time += rel_time;\
|
|
m.dsp_time -= rel_time;\
|
|
m.timers [0].next_time -= rel_time;\
|
|
m.timers [1].next_time -= rel_time;\
|
|
m.timers [2].next_time -= rel_time;\
|
|
assert( m.spc_time <= end_time );\
|
|
return ®S [r_cpuio0];\
|
|
}
|
|
|
|
int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks
|
|
|
|
void Snes_Spc::end_frame( time_t end_time )
|
|
{
|
|
// Catch CPU up to as close to end as possible. If final instruction
|
|
// would exceed end, does NOT execute it and leaves m.spc_time < end.
|
|
if ( end_time > m.spc_time )
|
|
run_until_( end_time );
|
|
|
|
m.spc_time -= end_time;
|
|
m.extra_clocks += end_time;
|
|
|
|
// Greatest number of clocks early that emulation can stop early due to
|
|
// not being able to execute current instruction without going over
|
|
// allowed time.
|
|
assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 );
|
|
|
|
// Catch timers up to CPU
|
|
for ( int i = 0; i < timer_count; i++ )
|
|
run_timer( &m.timers [i], 0 );
|
|
|
|
// Catch DSP up to CPU
|
|
if ( m.dsp_time < 0 )
|
|
{
|
|
RUN_DSP( 0, max_reg_time );
|
|
}
|
|
|
|
// Save any extra samples beyond what should be generated
|
|
if ( m.buf_begin )
|
|
save_extra();
|
|
}
|
|
|
|
// Inclusion here allows static memory access functions and better optimization
|
|
#include "Spc_Cpu.h"
|