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