diff Plugins/Input/console/Nes_Cpu.cpp @ 493:c04dff121e1d trunk

[svn] hostile merge, phase 2: reimport based on new plugin code
author nenolod
date Tue, 24 Jan 2006 20:19:01 -0800
parents
children f12d7e208b43
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Input/console/Nes_Cpu.cpp	Tue Jan 24 20:19:01 2006 -0800
@@ -0,0 +1,950 @@
+
+// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/nes-emu/
+
+#include "Nes_Cpu.h"
+
+#include <string.h>
+#include <limits.h>
+
+#include "blargg_endian.h"
+
+/* Copyright (C) 2003-2006 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include BLARGG_SOURCE_BEGIN
+
+#if BLARGG_NONPORTABLE
+	#define PAGE_OFFSET( addr ) (addr)
+#else
+	#define PAGE_OFFSET( addr ) ((addr) & (page_size - 1))
+#endif
+
+Nes_Cpu::Nes_Cpu()
+{
+	callback_data = NULL;
+	reset();
+}
+
+inline void Nes_Cpu::set_code_page( int i, uint8_t const* p )
+{
+	code_map [i] = p - PAGE_OFFSET( i * page_size );
+}
+
+void Nes_Cpu::reset( const void* unmapped_code_page, reader_t read, writer_t write )
+{
+	r.status = 0;
+	r.sp = 0;
+	r.pc = 0;
+	r.a = 0;
+	r.x = 0;
+	r.y = 0;
+	
+	clock_count = 0;
+	base_time = 0;
+	clock_limit = 0;
+	irq_time_ = LONG_MAX / 2 + 1;
+	end_time_ = LONG_MAX / 2 + 1;
+	
+	for ( int i = 0; i < page_count + 1; i++ )
+	{
+		set_code_page( i, (uint8_t*) unmapped_code_page );
+		data_reader [i] = read;
+		data_writer [i] = write;
+	}
+}
+
+void Nes_Cpu::map_code( nes_addr_t start, unsigned long size, const void* data )
+{
+	// address range must begin and end on page boundaries
+	require( start % page_size == 0 );
+	require( size % page_size == 0 );
+	require( start + size <= 0x10000 );
+	
+	unsigned first_page = start / page_size;
+	for ( unsigned i = size / page_size; i--; )
+		set_code_page( first_page + i, (uint8_t*) data + i * page_size );
+}
+
+void Nes_Cpu::set_reader( nes_addr_t start, unsigned long size, reader_t func )
+{
+	// address range must begin and end on page boundaries
+	require( start % page_size == 0 );
+	require( size % page_size == 0 );
+	require( start + size <= 0x10000 + page_size );
+	
+	unsigned first_page = start / page_size;
+	for ( unsigned i = size / page_size; i--; )
+		data_reader [first_page + i] = func;
+}
+
+void Nes_Cpu::set_writer( nes_addr_t start, unsigned long size, writer_t func )
+{
+	// address range must begin and end on page boundaries
+	require( start % page_size == 0 );
+	require( size % page_size == 0 );
+	require( start + size <= 0x10000 + page_size );
+	
+	unsigned first_page = start / page_size;
+	for ( unsigned i = size / page_size; i--; )
+		data_writer [first_page + i] = func;
+}
+
+// Note: 'addr' is evaulated more than once in the following macros, so it
+// must not contain side-effects.
+
+#define READ( addr )        (data_reader [(addr) >> page_bits]( callback_data, addr ))
+#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data ))
+
+#define READ_LOW( addr )        (low_mem [int (addr)])
+#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data))
+
+#define READ_PROG( addr )   (code_map [(addr) >> page_bits] [PAGE_OFFSET( addr )])
+#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) )
+
+#define SET_SP( v )     (sp = ((v) + 1) | 0x100)
+#define GET_SP()        ((sp - 1) & 0xff)
+
+#define PUSH( v )       ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v ))
+
+int Nes_Cpu::read( nes_addr_t addr )
+{
+	return READ( addr );
+}
+
+void Nes_Cpu::write( nes_addr_t addr, int value )
+{
+	WRITE( addr, value );
+}
+
+#ifndef NES_CPU_GLUE_ONLY
+
+static const unsigned char clock_table [256] = {
+//  0 1 2 3 4 5 6 7 8 9 A B C D E F
+	7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0
+	3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1
+	6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2
+	3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3
+	6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4
+	3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5
+	6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6
+	3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7
+	2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8
+	3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9
+	2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A
+	3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B
+	2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C
+	3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D
+	2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6,// E
+	3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F
+};
+
+#include BLARGG_ENABLE_OPTIMIZER
+
+Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end )
+{
+	set_end_time( end );
+	
+	volatile result_t result = result_cycles;
+	
+#if BLARGG_CPU_POWERPC
+	// cache commonly-used values in registers
+	long clock_count = this->clock_count;
+	writer_t* const data_writer = this->data_writer;
+	reader_t* const data_reader = this->data_reader;
+	uint8_t* const low_mem = this->low_mem;
+#endif
+	
+	// registers
+	unsigned pc = r.pc;
+	int sp;
+	SET_SP( r.sp );
+	int a = r.a;
+	int x = r.x;
+	int y = r.y;
+	
+	// status flags
+	
+	const int st_n = 0x80;
+	const int st_v = 0x40;
+	const int st_r = 0x20;
+	const int st_b = 0x10;
+	const int st_d = 0x08;
+	const int st_i = 0x04;
+	const int st_z = 0x02;
+	const int st_c = 0x01;
+	
+	#define IS_NEG (nz & 0x880)
+	
+	#define CALC_STATUS( out ) do {             \
+		out = status & (st_v | st_d | st_i);    \
+		out |= (c >> 8) & st_c;                 \
+		if ( IS_NEG ) out |= st_n;              \
+		if ( !(nz & 0xFF) ) out |= st_z;        \
+	} while ( false )
+
+	#define SET_STATUS( in ) do {               \
+		status = in & (st_v | st_d | st_i);     \
+		c = in << 8;                            \
+		nz = (in << 4) & 0x800;                 \
+		nz |= ~in & st_z;                       \
+	} while ( false )
+	
+	int status;
+	int c;  // carry set if (c & 0x100) != 0
+	int nz; // Z set if (nz & 0xff) == 0, N set if (nz & 0x880) != 0
+	{
+		int temp = r.status;
+		SET_STATUS( temp );
+	}
+	
+	goto loop;
+dec_clock_loop:
+	clock_count--;
+loop:
+	
+	assert( unsigned (GET_SP()) < 0x100 );
+	assert( unsigned (a) < 0x100 );
+	assert( unsigned (x) < 0x100 );
+	assert( unsigned (y) < 0x100 );
+	
+	uint8_t const* page = code_map [pc >> page_bits];
+	unsigned opcode = page [PAGE_OFFSET( pc )];
+	unsigned data = page [PAGE_OFFSET( pc ) + 1];
+	pc++;
+	
+	// page crossing
+	//check( opcode == 0x60 || &READ_PROG( pc ) == &page [PAGE_OFFSET( pc )] );
+	
+	if ( clock_count >= clock_limit )
+		goto stop;
+	
+	clock_count += clock_table [opcode];
+	
+	#if BLARGG_CPU_POWERPC
+		this->clock_count = clock_count;
+	#endif
+	
+	switch ( opcode )
+	{
+
+// Macros
+
+#define ADD_PAGE        (pc++, data += 0x100 * READ_PROG( pc ));
+#define GET_ADDR()      READ_PROG16( pc )
+
+#define HANDLE_PAGE_CROSSING( lsb ) clock_count += (lsb) >> 8;
+
+#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop;
+
+#define IND_Y {                                                 \
+		int temp = READ_LOW( data ) + y;                        \
+		data = temp + 0x100 * READ_LOW( uint8_t (data + 1) );   \
+		HANDLE_PAGE_CROSSING( temp );                           \
+	}
+	
+#define IND_X {                                                 \
+		int temp = data + x;                                    \
+		data = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \
+	}
+	
+#define ARITH_ADDR_MODES( op )          \
+case op - 0x04: /* (ind,x) */           \
+	IND_X                               \
+	goto ptr##op;                       \
+case op + 0x0C: /* (ind),y */           \
+	IND_Y                               \
+	goto ptr##op;                       \
+case op + 0x10: /* zp,X */              \
+	data = uint8_t (data + x);          \
+case op + 0x00: /* zp */                \
+	data = READ_LOW( data );            \
+	goto imm##op;                       \
+case op + 0x14: /* abs,Y */             \
+	data += y;                          \
+	goto ind##op;                       \
+case op + 0x18: /* abs,X */             \
+	data += x;                          \
+ind##op:                                \
+	HANDLE_PAGE_CROSSING( data );       \
+case op + 0x08: /* abs */               \
+	ADD_PAGE                            \
+ptr##op:                                \
+	data = READ( data );                \
+case op + 0x04: /* imm */               \
+imm##op:                                \
+
+#define BRANCH( cond )      \
+{                           \
+	pc++;                   \
+	int offset = (BOOST::int8_t) data;  \
+	int extra_clock = (pc & 0xff) + offset; \
+	if ( !(cond) ) goto dec_clock_loop; \
+	pc += offset;       \
+	clock_count += (extra_clock >> 8) & 1;  \
+	goto loop;          \
+}
+
+// Often-Used
+
+	case 0xB5: // LDA zp,x
+		data = uint8_t (data + x);
+	case 0xA5: // LDA zp
+		a = nz = READ_LOW( data );
+		pc++;
+		goto loop;
+	
+	case 0xD0: // BNE
+		BRANCH( (uint8_t) nz );
+	
+	case 0x20: { // JSR
+		int temp = pc + 1;
+		pc = READ_PROG16( pc );
+		WRITE_LOW( 0x100 | (sp - 1), temp >> 8 );
+		sp = (sp - 2) | 0x100;
+		WRITE_LOW( sp, temp );
+		goto loop;
+	}
+	
+	case 0x4C: // JMP abs
+		pc = READ_PROG16( pc );
+		goto loop;
+	
+	case 0xE8: INC_DEC_XY( x, 1 )  // INX
+	
+	case 0x10: // BPL
+		BRANCH( !IS_NEG )
+	
+	ARITH_ADDR_MODES( 0xC5 ) // CMP
+		nz = a - data;
+		pc++;
+		c = ~nz;
+		nz &= 0xff;
+		goto loop;
+	
+	case 0x30: // BMI
+		BRANCH( IS_NEG )
+	
+	case 0xF0: // BEQ
+		BRANCH( !(uint8_t) nz );
+	
+	case 0x95: // STA zp,x
+		data = uint8_t (data + x);
+	case 0x85: // STA zp
+		pc++;
+		WRITE_LOW( data, a );
+		goto loop;
+	
+	case 0xC8: INC_DEC_XY( y, 1 )  // INY
+
+	case 0xA8: // TAY
+		y = a;
+	case 0x98: // TYA
+		a = nz = y;
+		goto loop;
+	
+	case 0xB9: // LDA abs,Y
+		data += y;
+		goto lda_ind_common;
+	
+	case 0xBD: // LDA abs,X
+		data += x;
+	lda_ind_common:
+		HANDLE_PAGE_CROSSING( data );
+	case 0xAD: // LDA abs
+		ADD_PAGE
+	lda_ptr:
+		a = nz = READ( data );
+		pc++;
+		goto loop;
+	
+	case 0x60: // RTS
+		pc = 1 + READ_LOW( sp );
+		pc += READ_LOW( 0x100 | (sp - 0xff) ) * 0x100;
+		sp = (sp - 0xfe) | 0x100;
+		goto loop;
+
+	case 0x99: // STA abs,Y
+		data += y;
+		goto sta_ind_common;
+	
+	case 0x9D: // STA abs,X
+		data += x;
+	sta_ind_common:
+		HANDLE_PAGE_CROSSING( data );
+	case 0x8D: // STA abs
+		ADD_PAGE
+	sta_ptr:
+		pc++;
+		WRITE( data, a );
+		goto loop;
+	
+	case 0xA9: // LDA #imm
+		pc++;
+		a = data;
+		nz = data;
+		goto loop;
+
+// Branch
+
+	case 0x50: // BVC
+		BRANCH( !(status & st_v) )
+	
+	case 0x70: // BVS
+		BRANCH( status & st_v )
+	
+	case 0xB0: // BCS
+		BRANCH( c & 0x100 )
+	
+	case 0x90: // BCC
+		BRANCH( !(c & 0x100) )
+	
+// Load/store
+	
+	case 0x94: // STY zp,x
+		data = uint8_t (data + x);
+	case 0x84: // STY zp
+		pc++;
+		WRITE_LOW( data, y );
+		goto loop;
+	
+	case 0x96: // STX zp,y
+		data = uint8_t (data + y);
+	case 0x86: // STX zp
+		pc++;
+		WRITE_LOW( data, x );
+		goto loop;
+	
+	case 0xB6: // LDX zp,y
+		data = uint8_t (data + y);
+	case 0xA6: // LDX zp
+		data = READ_LOW( data );
+	case 0xA2: // LDX #imm
+		pc++;
+		x = data;
+		nz = data;
+		goto loop;
+	
+	case 0xB4: // LDY zp,x
+		data = uint8_t (data + x);
+	case 0xA4: // LDY zp
+		data = READ_LOW( data );
+	case 0xA0: // LDY #imm
+		pc++;
+		y = data;
+		nz = data;
+		goto loop;
+	
+	case 0xB1: // LDA (ind),Y
+		IND_Y
+		goto lda_ptr;
+	
+	case 0xA1: // LDA (ind,X)
+		IND_X
+		goto lda_ptr;
+	
+	case 0x91: // STA (ind),Y
+		IND_Y
+		goto sta_ptr;
+	
+	case 0x81: // STA (ind,X)
+		IND_X
+		goto sta_ptr;
+	
+	case 0xBC: // LDY abs,X
+		data += x;
+		HANDLE_PAGE_CROSSING( data );
+	case 0xAC:{// LDY abs
+		pc++;
+		unsigned addr = data + 0x100 * READ_PROG( pc );
+		pc++;
+		y = nz = READ( addr );
+		goto loop;
+	}
+	
+	case 0xBE: // LDX abs,y
+		data += y;
+		HANDLE_PAGE_CROSSING( data );
+	case 0xAE:{// LDX abs
+		pc++;
+		unsigned addr = data + 0x100 * READ_PROG( pc );
+		pc++;
+		x = nz = READ( addr );
+		goto loop;
+	}
+	
+	{
+		int temp;
+	case 0x8C: // STY abs
+		temp = y;
+		goto store_abs;
+	
+	case 0x8E: // STX abs
+		temp = x;
+	store_abs:
+		unsigned addr = GET_ADDR();
+		WRITE( addr, temp );
+		pc += 2;
+		goto loop;
+	}
+
+// Compare
+
+	case 0xEC:{// CPX abs
+		unsigned addr = GET_ADDR();
+		pc++;
+		data = READ( addr );
+		goto cpx_data;
+	}
+	
+	case 0xE4: // CPX zp
+		data = READ_LOW( data );
+	case 0xE0: // CPX #imm
+	cpx_data:
+		nz = x - data;
+		pc++;
+		c = ~nz;
+		nz &= 0xff;
+		goto loop;
+	
+	case 0xCC:{// CPY abs
+		unsigned addr = GET_ADDR();
+		pc++;
+		data = READ( addr );
+		goto cpy_data;
+	}
+	
+	case 0xC4: // CPY zp
+		data = READ_LOW( data );
+	case 0xC0: // CPY #imm
+	cpy_data:
+		nz = y - data;
+		pc++;
+		c = ~nz;
+		nz &= 0xff;
+		goto loop;
+	
+// Logical
+
+	ARITH_ADDR_MODES( 0x25 ) // AND
+		nz = (a &= data);
+		pc++;
+		goto loop;
+	
+	ARITH_ADDR_MODES( 0x45 ) // EOR
+		nz = (a ^= data);
+		pc++;
+		goto loop;
+	
+	ARITH_ADDR_MODES( 0x05 ) // ORA
+		nz = (a |= data);
+		pc++;
+		goto loop;
+	
+	case 0x2C:{// BIT abs
+		unsigned addr = GET_ADDR();
+		pc++;
+		nz = READ( addr );
+		goto bit_common;
+	}
+	
+	case 0x24: // BIT zp
+		nz = READ_LOW( data );
+	bit_common:
+		pc++;
+		status &= ~st_v;
+		status |= nz & st_v;
+		// if result is zero, might also be negative, so use secondary N bit
+		if ( !(a & nz) )
+			nz = (nz << 4) & 0x800;
+		goto loop;
+		
+// Add/subtract
+
+	ARITH_ADDR_MODES( 0xE5 ) // SBC
+	case 0xEB: // unofficial equivalent
+		data ^= 0xFF;
+		goto adc_imm;
+	
+	ARITH_ADDR_MODES( 0x65 ) // ADC
+	adc_imm: {
+		int carry = (c >> 8) & 1;
+		int ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend
+		status &= ~st_v;
+		status |= (ov >> 2) & 0x40;
+		c = nz = a + data + carry;
+		pc++;
+		a = (uint8_t) nz;
+		goto loop;
+	}
+	
+// Shift/rotate
+
+	case 0x4A: // LSR A
+		c = 0;
+	case 0x6A: // ROR A
+		nz = (c >> 1) & 0x80; // could use bit insert macro here
+		c = a << 8;
+		nz |= a >> 1;
+		a = nz;
+		goto loop;
+
+	case 0x0A: // ASL A
+		nz = a << 1;
+		c = nz;
+		a = (uint8_t) nz;
+		goto loop;
+
+	case 0x2A: { // ROL A
+		nz = a << 1;
+		int temp = (c >> 8) & 1;
+		c = nz;
+		nz |= temp;
+		a = (uint8_t) nz;
+		goto loop;
+	}
+	
+	case 0x3E: // ROL abs,X
+		data += x;
+		goto rol_abs;
+	
+	case 0x1E: // ASL abs,X
+		data += x;
+	case 0x0E: // ASL abs
+		c = 0;
+	case 0x2E: // ROL abs
+	rol_abs:
+		HANDLE_PAGE_CROSSING( data );
+		ADD_PAGE
+		nz = (c >> 8) & 1;
+		nz |= (c = READ( data ) << 1);
+	rotate_common:
+		pc++;
+		WRITE( data, (uint8_t) nz );
+		goto loop;
+	
+	case 0x7E: // ROR abs,X
+		data += x;
+		goto ror_abs;
+	
+	case 0x5E: // LSR abs,X
+		data += x;
+	case 0x4E: // LSR abs
+		c = 0;
+	case 0x6E: // ROR abs
+	ror_abs: {
+		HANDLE_PAGE_CROSSING( data );
+		ADD_PAGE
+		int temp = READ( data );
+		nz = ((c >> 1) & 0x80) | (temp >> 1);
+		c = temp << 8;
+		goto rotate_common;
+	}
+	
+	case 0x76: // ROR zp,x
+		data = uint8_t (data + x);
+		goto ror_zp;
+	
+	case 0x56: // LSR zp,x
+		data = uint8_t (data + x);
+	case 0x46: // LSR zp
+		c = 0;
+	case 0x66: // ROR zp
+	ror_zp: {
+		int temp = READ_LOW( data );
+		nz = ((c >> 1) & 0x80) | (temp >> 1);
+		c = temp << 8;
+		goto write_nz_zp;
+	}
+	
+	case 0x36: // ROL zp,x
+		data = uint8_t (data + x);
+		goto rol_zp;
+	
+	case 0x16: // ASL zp,x
+		data = uint8_t (data + x);
+	case 0x06: // ASL zp
+		c = 0;
+	case 0x26: // ROL zp
+	rol_zp:
+		nz = (c >> 8) & 1;
+		nz |= (c = READ_LOW( data ) << 1);
+		goto write_nz_zp;
+	
+// Increment/decrement
+
+	case 0xCA: INC_DEC_XY( x, -1 ) // DEX
+	
+	case 0x88: INC_DEC_XY( y, -1 ) // DEY
+	
+	case 0xF6: // INC zp,x
+		data = uint8_t (data + x);
+	case 0xE6: // INC zp
+		nz = 1;
+		goto add_nz_zp;
+	
+	case 0xD6: // DEC zp,x
+		data = uint8_t (data + x);
+	case 0xC6: // DEC zp
+		nz = -1;
+	add_nz_zp:
+		nz += READ_LOW( data );
+	write_nz_zp:
+		pc++;
+		WRITE_LOW( data, nz );
+		goto loop;
+	
+	case 0xFE: // INC abs,x
+		HANDLE_PAGE_CROSSING( data + x );
+		data = x + GET_ADDR();
+		goto inc_ptr;
+	
+	case 0xEE: // INC abs
+		data = GET_ADDR();
+	inc_ptr:
+		nz = 1;
+		goto inc_common;
+	
+	case 0xDE: // DEC abs,x
+		HANDLE_PAGE_CROSSING( data + x );
+		data = x + GET_ADDR();
+		goto dec_ptr;
+	
+	case 0xCE: // DEC abs
+		data = GET_ADDR();
+	dec_ptr:
+		nz = -1;
+	inc_common:
+		nz += READ( data );
+		pc += 2;
+		WRITE( data, (uint8_t) nz );
+		goto loop;
+		
+// Transfer
+
+	case 0xAA: // TAX
+		x = a;
+	case 0x8A: // TXA
+		a = nz = x;
+		goto loop;
+
+	case 0x9A: // TXS
+		SET_SP( x ); // verified (no flag change)
+		goto loop;
+	
+	case 0xBA: // TSX
+		x = nz = GET_SP();
+		goto loop;
+	
+// Stack
+	
+	case 0x48: // PHA
+		PUSH( a ); // verified
+		goto loop;
+		
+	case 0x68: // PLA
+		a = nz = READ_LOW( sp );
+		sp = (sp - 0xff) | 0x100;
+		goto loop;
+		
+	case 0x40: // RTI
+		{
+			int temp = READ_LOW( sp );
+			pc   = READ_LOW( 0x100 | (sp - 0xff) );
+			pc  |= READ_LOW( 0x100 | (sp - 0xfe) ) * 0x100;
+			sp = (sp - 0xfd) | 0x100;
+			data = status;
+			SET_STATUS( temp );
+		}
+		if ( !((data ^ status) & st_i) )
+			goto loop; // I flag didn't change
+	i_flag_changed:
+		//dprintf( "%6d %s\n", time(), (status & st_i ? "SEI" : "CLI") );
+		this->r.status = status; // update externally-visible I flag
+		// update clock_limit based on modified I flag
+		clock_limit = end_time_;
+		if ( end_time_ <= irq_time_ )
+			goto loop;
+		if ( status & st_i )
+			goto loop;
+		clock_limit = irq_time_;
+		goto loop;
+	
+	case 0x28:{// PLP
+		int temp = READ_LOW( sp );
+		sp = (sp - 0xff) | 0x100;
+		data = status;
+		SET_STATUS( temp );
+		if ( !((data ^ status) & st_i) )
+			goto loop; // I flag didn't change
+		if ( !(status & st_i) )
+			goto handle_cli;
+		goto handle_sei;
+	}
+	
+	case 0x08: { // PHP
+		int temp;
+		CALC_STATUS( temp );
+		PUSH( temp | st_b | st_r );
+		goto loop;
+	}
+	
+	case 0x6C: // JMP (ind)
+		data = GET_ADDR();
+		pc = READ( data );
+		pc |= READ( (data & 0xff00) | ((data + 1) & 0xff) ) << 8;
+		goto loop;
+	
+	case 0x00: { // BRK
+		pc++;
+		WRITE_LOW( 0x100 | (sp - 1), pc >> 8 );
+		WRITE_LOW( 0x100 | (sp - 2), pc );
+		int temp;
+		CALC_STATUS( temp );
+		sp = (sp - 3) | 0x100;
+		WRITE_LOW( sp, temp | st_b | st_r );
+		pc = READ_PROG16( 0xFFFE );
+		status |= st_i;
+		goto i_flag_changed;
+	}
+	
+// Flags
+
+	case 0x38: // SEC
+		c = ~0;
+		goto loop;
+	
+	case 0x18: // CLC
+		c = 0;
+		goto loop;
+		
+	case 0xB8: // CLV
+		status &= ~st_v;
+		goto loop;
+	
+	case 0xD8: // CLD
+		status &= ~st_d;
+		goto loop;
+	
+	case 0xF8: // SED
+		status |= st_d;
+		goto loop;
+	
+	case 0x58: // CLI
+		if ( !(status & st_i) )
+			goto loop;
+		status &= ~st_i;
+	handle_cli:
+		//dprintf( "%6d CLI\n", time() );
+		this->r.status = status; // update externally-visible I flag
+		if ( clock_count < end_time_ )
+		{
+			assert( clock_limit == end_time_ );
+			if ( end_time_ <= irq_time_ )
+				goto loop; // irq is later
+			if ( clock_count >= irq_time_ )
+				irq_time_ = clock_count + 1; // delay IRQ until after next instruction
+			clock_limit = irq_time_;
+			goto loop;
+		}
+		// execution is stopping now, so delayed CLI must be handled by caller
+		result = result_cli;
+		goto end;
+		
+	case 0x78: // SEI
+		if ( status & st_i )
+			goto loop;
+		status |= st_i;
+	handle_sei:
+		//dprintf( "%6d SEI\n", time() );
+		this->r.status = status; // update externally-visible I flag
+		clock_limit = end_time_;
+		if ( clock_count < irq_time_ )
+			goto loop;
+		result = result_sei; // IRQ will occur now, even though I flag is set
+		goto end;
+
+// Undocumented
+
+	case 0x0C: case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: // SKW
+		pc++;
+	case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: // SKB
+	case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4:
+		pc++;
+	case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP
+		goto loop;
+
+// Unimplemented
+	
+	case page_wrap_opcode: // HLT
+		if ( pc > 0x10000 )
+		{
+			// handle wrap-around (assumes caller has put page of HLT at 0x10000)
+			pc = (pc - 1) & 0xffff;
+			clock_count -= 2;
+			goto loop;
+		}
+		// fall through
+	case 0x02: case 0x12: case 0x22: case 0x32: // HLT
+	case 0x42: case 0x52: case 0x62: case 0x72:
+	case 0x92: case 0xB2: case 0xD2:
+	case 0x9B: // TAS
+	case 0x9C: // SAY
+	case 0x9E: // XAS
+	case 0x93: // AXA
+	case 0x9F: // AXA
+	case 0x0B: // ANC
+	case 0x2B: // ANC
+	case 0xBB: // LAS
+	case 0x4B: // ALR
+	case 0x6B: // AAR
+	case 0x8B: // XAA
+	case 0xAB: // OAL
+	case 0xCB: // SAX
+	case 0x83: case 0x87: case 0x8F: case 0x97: // AXS
+	case 0xA3: case 0xA7: case 0xAF: case 0xB3: case 0xB7: case 0xBF: // LAX
+	case 0xE3: case 0xE7: case 0xEF: case 0xF3: case 0xF7: case 0xFB: case 0xFF: // INS
+	case 0xC3: case 0xC7: case 0xCF: case 0xD3: case 0xD7: case 0xDB: case 0xDF: // DCM
+	case 0x63: case 0x67: case 0x6F: case 0x73: case 0x77: case 0x7B: case 0x7F: // RRA
+	case 0x43: case 0x47: case 0x4F: case 0x53: case 0x57: case 0x5B: case 0x5F: // LSE
+	case 0x23: case 0x27: case 0x2F: case 0x33: case 0x37: case 0x3B: case 0x3F: // RLA
+	case 0x03: case 0x07: case 0x0F: case 0x13: case 0x17: case 0x1B: case 0x1F: // ASO
+		result = result_badop;
+		goto stop;
+	}
+	
+	// If this fails then the case above is missing an opcode
+	assert( false );
+	
+stop:
+	pc--;
+end:
+	
+	{
+		int temp;
+		CALC_STATUS( temp );
+		r.status = temp;
+	}
+	
+	base_time += clock_count;
+	clock_limit -= clock_count;
+	this->clock_count = 0;
+	r.pc = pc;
+	r.sp = GET_SP();
+	r.a = a;
+	r.x = x;
+	r.y = y;
+	irq_time_ = LONG_MAX / 2 + 1;
+	
+	return result;
+}
+
+#endif
+