diff src/console/Snes_Spc.cxx @ 316:fb513e10174e trunk

[svn] - merge libconsole-blargg into mainline libconsole: + obsoletes plugins-ugly:sapplug
author nenolod
date Thu, 30 Nov 2006 19:54:33 -0800
parents 3da1b8942b8b
children 986f098da058
line wrap: on
line diff
--- a/src/console/Snes_Spc.cxx	Wed Nov 29 14:42:11 2006 -0800
+++ b/src/console/Snes_Spc.cxx	Thu Nov 30 19:54:33 2006 -0800
@@ -1,9 +1,7 @@
-
-// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
 
 #include "Snes_Spc.h"
 
-#include <assert.h>
 #include <string.h>
 
 /* Copyright (C) 2004-2006 Shay Green. This module is free software; you
@@ -12,29 +10,46 @@
 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 */
+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_BEGIN
+#include "blargg_source.h"
 
 // always in the future (CPU time can go over 0, but not by this much)
 int const timer_disabled_time = 127;
 
 Snes_Spc::Snes_Spc() : dsp( ram ), cpu( this, ram )
 {
-	timer [0].shift = 7; // 8 kHz
-	timer [1].shift = 7; // 8 kHz
-	timer [2].shift = 4; // 64 kHz
+	set_tempo( 1.0 );
 	
 	// Put STOP instruction past end of memory to catch PC overflow.
-	memset( ram + ram_size, 0xff, (sizeof ram) - ram_size );
+	memset( ram + ram_size, 0xFF, (sizeof ram) - ram_size );
+	
+	// A few tracks read from the last four bytes of IPL ROM
+	boot_rom [sizeof boot_rom - 2] = 0xC0;
+	boot_rom [sizeof boot_rom - 1] = 0xFF;
+	memset( boot_rom, 0, sizeof boot_rom - 2 );
+}
+
+void Snes_Spc::set_tempo( double t )
+{
+	int unit = (int) (16.0 / t + 0.5);
+	
+	timer [0].divisor = unit * 8; // 8 kHz
+	timer [1].divisor = unit * 8; // 8 kHz
+	timer [2].divisor = unit;     // 64 kHz
 }
 
 // Load
 
-blargg_err_t Snes_Spc::load_spc( const void* data, long size, bool clear_echo_ )
+void Snes_Spc::set_ipl_rom( void const* in )
+{
+	memcpy( boot_rom, in, sizeof boot_rom );
+}
+
+blargg_err_t Snes_Spc::load_spc( const void* data, long size )
 {
 	struct spc_file_t {
 		char    signature [27];
@@ -48,10 +63,11 @@
 		char    unused2 [212];
 		uint8_t ram [0x10000];
 		uint8_t dsp [128];
+		uint8_t ipl_rom [128];
 	};
-	BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size );
+	BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size + 128 );
 	
-	const spc_file_t* spc = (spc_file_t*) data;
+	const spc_file_t* spc = (spc_file_t const*) data;
 	
 	if ( size < spc_file_size )
 		return "Not an SPC file";
@@ -60,31 +76,30 @@
 		return "Not an SPC file";
 	
 	registers_t regs;
-	regs.pc = spc->pc [1] * 0x100 + spc->pc [0];
-	regs.a = spc->a;
-	regs.x = spc->x;
-	regs.y = spc->y;
+	regs.pc     = spc->pc [1] * 0x100 + spc->pc [0];
+	regs.a      = spc->a;
+	regs.x      = spc->x;
+	regs.y      = spc->y;
 	regs.status = spc->status;
-	regs.sp = spc->sp;
+	regs.sp     = spc->sp;
+	
+	if ( (unsigned long) size >= sizeof *spc )
+		set_ipl_rom( spc->ipl_rom );
 	
 	const char* error = load_state( regs, spc->ram, spc->dsp );
 	
 	echo_accessed = false;
 	
-	if ( clear_echo_ )
-		clear_echo();
-	
 	return error;
 }
 
 void Snes_Spc::clear_echo()
 {
-	if ( !(dsp.read( 0x6c ) & 0x20) )
+	if ( !(dsp.read( 0x6C ) & 0x20) )
 	{
-		unsigned addr = 0x100 * dsp.read( 0x6d );
-		unsigned size = 0x800 * dsp.read( 0x7d );
-		unsigned limit = ram_size - addr;
-		memset( ram + addr, 0xff, (size < limit) ? size : limit );
+		unsigned addr = 0x100 * dsp.read( 0x6D );
+		unsigned size = 0x800 * dsp.read( 0x7D );
+		memset( ram + addr, 0xFF, min( size, ram_size - addr ) );
 	}
 }
 
@@ -108,14 +123,14 @@
 	memcpy( extra_ram, ram + rom_addr, sizeof extra_ram );
 	
 	// boot rom (have to force enable_rom() to update it)
-	rom_enabled = !(ram [0xf1] & 0x80);
+	rom_enabled = !(ram [0xF1] & 0x80);
 	enable_rom( !rom_enabled );
 	
 	// dsp
 	dsp.reset();
 	int i;
 	for ( i = 0; i < Spc_Dsp::register_count; i++ )
-		dsp.write( i, ((uint8_t*) dsp_state) [i] );
+		dsp.write( i, ((uint8_t const*) dsp_state) [i] );
 	
 	// timers
 	for ( i = 0; i < timer_count; i++ )
@@ -123,29 +138,29 @@
 		Timer& t = timer [i];
 		
 		t.next_tick = 0;
-		t.enabled = (ram [0xf1] >> i) & 1;
+		t.enabled = (ram [0xF1] >> i) & 1;
 		if ( !t.enabled )
 			t.next_tick = timer_disabled_time;
 		t.count = 0;
-		t.counter = ram [0xfd + i] & 15;
+		t.counter = ram [0xFD + i] & 15;
 		
-		int p = ram [0xfa + i];
+		int p = ram [0xFA + i];
 		t.period = p ? p : 0x100;
 	}
 	
 	// Handle registers which already give 0 when read by setting RAM and not changing it.
 	// Put STOP instruction in registers which can be read, to catch attempted CPU execution.
-	ram [0xf0] = 0;
-	ram [0xf1] = 0;
-	ram [0xf3] = 0xff;
-	ram [0xfa] = 0;
-	ram [0xfb] = 0;
-	ram [0xfc] = 0;
-	ram [0xfd] = 0xff;
-	ram [0xfe] = 0xff;
-	ram [0xff] = 0xff;
+	ram [0xF0] = 0;
+	ram [0xF1] = 0;
+	ram [0xF3] = 0xFF;
+	ram [0xFA] = 0;
+	ram [0xFB] = 0;
+	ram [0xFC] = 0;
+	ram [0xFD] = 0xFF;
+	ram [0xFE] = 0xFF;
+	ram [0xFF] = 0xFF;
 	
-	return NULL; // success
+	return 0; // success
 }
 
 // Hardware
@@ -166,10 +181,12 @@
 		dprintf( "next_tick: %ld, time: %ld", (long) next_tick, (long) time );
 	assert( enabled ); // when disabled, next_tick should always be in the future
 	
-	int elapsed = ((time - next_tick) >> shift) + 1;
-	next_tick += elapsed << shift;
+	int elapsed = ((time - next_tick) / divisor) + 1;
+	next_tick += elapsed * divisor;
+	
 	elapsed += count;
-	if ( elapsed >= period ) { // avoid costly divide
+	if ( elapsed >= period ) // avoid unnecessary division
+	{
 		int n = elapsed / period;
 		elapsed -= n * period;
 		counter = (counter + n) & 15;
@@ -203,13 +220,13 @@
 // inaccurate emulation due to the DSP not being caught up to the present.
 inline void Snes_Spc::check_for_echo_access( spc_addr_t addr )
 {
-	if ( !echo_accessed && !(dsp.read( 0x6c ) & 0x20) )
+	if ( !echo_accessed && !(dsp.read( 0x6C ) & 0x20) )
 	{
 		// ** If echo accesses are found that require running the DSP, cache
 		// the start and end address on DSP writes to speed up checking.
 		
-		unsigned start = 0x100 * dsp.read( 0x6d );
-		unsigned end = start + 0x800 * dsp.read( 0x7d );
+		unsigned start = 0x100 * dsp.read( 0x6D );
+		unsigned end = start + 0x800 * dsp.read( 0x7D );
 		if ( start <= addr && addr < end ) {
 			echo_accessed = true;
 			dprintf( "Read/write at $%04X within echo buffer\n", (unsigned) addr );
@@ -221,69 +238,66 @@
 
 int Snes_Spc::read( spc_addr_t addr )
 {
-	// zero page ram is used most often
-	if ( addr < 0xf0 )
-		return ram [addr];
+	int result = ram [addr];
+	
+	if ( (rom_addr <= addr && addr < 0xFFFC || addr >= 0xFFFE) && rom_enabled )
+		dprintf( "Read from ROM: %04X -> %02X\n", addr, result );
 	
-	// dsp
-	if ( addr == 0xf3 ) {
-		run_dsp( time() );
-		if ( ram [0xf2] >= Spc_Dsp::register_count )
-			dprintf( "DSP read from $%02X\n", (int) ram [0xf2] );
-		return dsp.read( ram [0xf2] & 0x7f );
+	if ( unsigned (addr - 0xF0) < 0x10 )
+	{
+		assert( 0xF0 <= addr && addr <= 0xFF );
+		
+		// counters
+		int i = addr - 0xFD;
+		if ( i >= 0 )
+		{
+			Timer& t = timer [i];
+			t.run_until( time() );
+			int result = t.counter;
+			t.counter = 0;
+			return result;
+		}
+		
+		// dsp
+		if ( addr == 0xF3 )
+		{
+			run_dsp( time() );
+			if ( ram [0xF2] >= Spc_Dsp::register_count )
+				dprintf( "DSP read from $%02X\n", (int) ram [0xF2] );
+			return dsp.read( ram [0xF2] & 0x7F );
+		}
+		
+		if ( addr == 0xF0 || addr == 0xF1 || addr == 0xF8 ||
+				addr == 0xF9 || addr == 0xFA )
+			dprintf( "Read from register $%02X\n", (int) addr );
+		
+		// Registers which always read as 0 are handled by setting ram [reg] to 0
+		// at startup then never changing that value.
+		
+		check(( check_for_echo_access( addr ), true ));
 	}
 	
-	// counters
-	unsigned i = addr - 0xfd; // negative converts to large positive unsigned
-	if ( i < timer_count ) {
-		Timer& t = timer [i];
-		t.run_until( time() );
-		int result = t.counter;
-		t.counter = 0;
-		return result;
-	}
-	
-	if ( addr == 0xf0 || addr == 0xf1 || addr == 0xf8 ||
-			addr == 0xf9 || addr == 0xfa )
-		dprintf( "Read from register $%02X\n", (int) addr );
-	
-	// Registers which always read as 0 are handled by setting ram [reg] to 0
-	// at startup then never changing that value.
-	
-	check(( check_for_echo_access( addr ), true ));
-	
-	// ram
-	return ram [addr];
+	return result;
 }
 
 
 // Write
 
-const unsigned char Snes_Spc::boot_rom [rom_size] = { // verified
-	0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0,
-	0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78,
-	0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
-	0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5,
-	0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
-	0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA,
-	0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD,
-	0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF
-};
-
 void Snes_Spc::enable_rom( bool enable )
 {
 	if ( rom_enabled != enable )
 	{
 		rom_enabled = enable;
 		memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size );
+		// TODO: ROM can still get overwritten when DSP writes to echo buffer
 	}
 }
 
 void Snes_Spc::write( spc_addr_t addr, int data )
 {
 	// first page is very common
-	if ( addr < 0xf0 ) {
-		ram [addr] = data;
+	if ( addr < 0xF0 ) {
+		ram [addr] = (uint8_t) data;
 	}
 	else switch ( addr )
 	{
@@ -291,20 +305,20 @@
 		default:
 			check(( check_for_echo_access( addr ), true ));
 			if ( addr < rom_addr ) {
-				ram [addr] = data;
+				ram [addr] = (uint8_t) data;
 			}
 			else {
-				extra_ram [addr - rom_addr] = data;
+				extra_ram [addr - rom_addr] = (uint8_t) data;
 				if ( !rom_enabled )
-					ram [addr] = data;
+					ram [addr] = (uint8_t) data;
 			}
 			break;
 		
 		// DSP
-		//case 0xf2: // mapped to RAM
-		case 0xf3: {
+		//case 0xF2: // mapped to RAM
+		case 0xF3: {
 			run_dsp( time() );
-			int reg = ram [0xf2];
+			int reg = ram [0xF2];
 			if ( next_dsp > 0 ) {
 				// skip mode
 				
@@ -327,12 +341,12 @@
 			break;
 		}
 		
-		case 0xf0: // Test register
+		case 0xF0: // Test register
 			dprintf( "Wrote $%02X to $F0\n", (int) data );
 			break;
 		
 		// Config
-		case 0xf1:
+		case 0xF1:
 		{
 			// timers
 			for ( int i = 0; i < timer_count; i++ )
@@ -353,12 +367,12 @@
 			
 			// port clears
 			if ( data & 0x10 ) {
-				ram [0xf4] = 0;
-				ram [0xf5] = 0;
+				ram [0xF4] = 0;
+				ram [0xF5] = 0;
 			}
 			if ( data & 0x20 ) {
-				ram [0xf6] = 0;
-				ram [0xf7] = 0;
+				ram [0xF6] = 0;
+				ram [0xF7] = 0;
 			}
 			
 			enable_rom( data & 0x80 );
@@ -367,22 +381,22 @@
 		}
 		
 		// Ports
-		case 0xf4:
-		case 0xf5:
-		case 0xf6:
-		case 0xf7:
+		case 0xF4:
+		case 0xF5:
+		case 0xF6:
+		case 0xF7:
 			// to do: handle output ports
 			break;
 		
-		//case 0xf8: // verified on SNES that these are read/write (RAM)
-		//case 0xf9:
+		//case 0xF8: // verified on SNES that these are read/write (RAM)
+		//case 0xF9:
 		
 		// Timers
-		case 0xfa:
-		case 0xfb:
-		case 0xfc: {
-			Timer& t = timer [addr - 0xfa];
-			if ( (t.period & 0xff) != data ) {
+		case 0xFA:
+		case 0xFB:
+		case 0xFC: {
+			Timer& t = timer [addr - 0xFA];
+			if ( (t.period & 0xFF) != data ) {
 				t.run_until( time() );
 				t.period = data ? data : 0x100;
 			}
@@ -390,11 +404,11 @@
 		}
 		
 		// Counters (cleared on write)
-		case 0xfd:
-		case 0xfe:
-		case 0xff:
+		case 0xFD:
+		case 0xFE:
+		case 0xFF:
 			dprintf( "Wrote to counter $%02X\n", (int) addr );
-			timer [addr - 0xfd].counter = 0;
+			timer [addr - 0xFD].counter = 0;
 			break;
 	}
 }
@@ -413,7 +427,7 @@
 		keys_pressed = 0;
 		keys_released = 0;
 		// sentinel tells play to ignore DSP
-		BLARGG_RETURN_ERR( play( count - sync_count, skip_sentinel ) );
+		RETURN_ERR( play( count - sync_count, skip_sentinel ) );
 		
 		// press/release keys now
 		dsp.write( 0x5C, keys_released & ~keys_pressed );
@@ -458,7 +472,7 @@
 	{
 		dprintf( "Unhandled instruction $%02X, pc = $%04X\n",
 				(int) cpu.read( cpu.r.pc ), (unsigned) cpu.r.pc );
-		return "Emulation error";
+		return "Emulation error (illegal/unsupported instruction)";
 	}
 	extra_cycles = -elapsed;
 	
@@ -468,8 +482,7 @@
 		assert( next_dsp == clocks_per_sample );
 		assert( out == skip_sentinel || sample_buf - out == count );
 	}
-	buf_end = NULL;
+	buf_end = 0;
 	
-	return blargg_success;
+	return 0;
 }
-