/*
 *  CPU.cpp - 6510 emulation
 *
 *  SIDPlayer (C) 1996-1997 Christian Bauer
 *

 *
 * Notes:
 * ------
 *
 *  - All memory accesses are done with the read_byte() and
 *    write_byte() functions which also do the memory address
 *    decoding. The read_zp() and write_zp() functions allow
 *    faster access to the zero page, the pop_byte() and
 *    push_byte() macros for the stack.
 *  - The PC is emulated with a direct memory pointer (for
 *    faster access). A second pointer, pc_base, is kept to
 *    allow recalculating the 16 bit 6510 PC if it has to be
 *    pushed on the stack.
 *  - The z_flag variable has the inverse meaning of the
 *    6510 Z flag
 *  - Only the highest bit of the n_flag variable is used
 */

#include <stdio.h>

#include "CPU.h"
#include "SID.h"


/*
 *  6510 constructor
 */

MOS6510::MOS6510() {}



/*
 *  Read a byte from the CPU's address space
 */

UBYTE MOS6510::read_byte(UWORD adr)
{
	if ((adr < 0xd000) || (adr >= 0xe000))
		return RAM[adr];
	else
		if ((adr & 0xfc00) == 0xd400)
			return TheSID->ReadRegister(adr & 0x7f);
		else
			return RAM[adr];
}


/*
 *  Read a word (little-endian) from the CPU's address space
 */

inline UWORD MOS6510::read_word(UWORD adr)
{
	return read_byte(adr) | (read_byte(adr+1) << 8);
}


/*
 *  Write a byte to the CPU's address space
 */

void MOS6510::write_byte(UWORD adr, UBYTE byte)
{
	if (adr < 0xd000 || adr >= 0xe000)
		RAM[adr] = byte;
	else
		if ((adr & 0xfc00) == 0xd400)
			TheSID->WriteRegister(adr & 0x7f, byte);
		else
			RAM[adr] = byte;
}


/*
 *  Read a byte from the zeropage
 */

inline UBYTE MOS6510::read_zp(UWORD adr)
{
	return RAM[adr];
}


/*
 *  Read a word (little-endian) from the zeropage
 */

inline UWORD MOS6510::read_zp_word(UWORD adr)
{
	return RAM[adr & 0xff] | (RAM[(adr+1) & 0xff] << 8);
}


/*
 *  Write a byte to the zeropage
 */

inline void MOS6510::write_zp(UWORD adr, UBYTE byte)
{
	RAM[adr] = byte;
}


/*
 *  Jump to address
 */

inline void MOS6510::jump(UWORD adr)
{
	pc = RAM + adr;
	pc_base = RAM;
}


/*
 *  Addressing mode macros
 */

// Read immediate operand
#define read_byte_imm() (*pc++)

// Read zeropage operand address
#define read_adr_zero() ((UWORD)read_byte_imm())

// Read zeropage operand
#define read_byte_zero() read_zp(read_adr_zero())

// Read zeropage x-indexed operand address
#define read_adr_zero_x() ((read_byte_imm() + x) & 0xff)

// Read zeropage x-indexed operand
#define read_byte_zero_x() read_zp(read_adr_zero_x())

// Read zeropage y-indexed operand address
#define read_adr_zero_y() ((read_byte_imm() + y) & 0xff)

// Read zeropage y-indexed operand
#define read_byte_zero_y() read_zp(read_adr_zero_y())

// Read absolute operand address (uses adr!)
#define	read_adr_abs() (adr = ((*(pc+1)) << 8) | *pc, pc+=2, adr)

// Read absolute operand
#define read_byte_abs() read_byte(read_adr_abs())

// Read absolute x-indexed operand address
#define read_adr_abs_x() (read_adr_abs() + x)

// Read absolute x-indexed operand
#define read_byte_abs_x() read_byte(read_adr_abs_x())

// Read absolute y-indexed operand address
#define read_adr_abs_y() (read_adr_abs() + y)

// Read absolute x-indexed operand
#define read_byte_abs_y() read_byte(read_adr_abs_y())

// Read indexed indirect operand address
#define read_adr_ind_x() (read_zp_word(read_byte_imm() + x))

// Read indexed indirect operand
#define read_byte_ind_x() read_byte(read_adr_ind_x())

// Read indirect indexed operand address
#define read_adr_ind_y() (read_zp_word(read_byte_imm()) + y)

// Read indirect indexed operand
#define read_byte_ind_y() read_byte(read_adr_ind_y())


/*
 *  Stack macros
 */

// Pop a byte from the stack
inline UBYTE MOS6510::pop_byte(void)
{
	if (sp == 0xff)
		stack_underflow = TRUE;
	return RAM[(++sp) & 0xff | 0x0100];
}

// Pop a word from the stack
inline UWORD MOS6510::pop_word(void)
{
	UBYTE tmp;

	tmp = pop_byte();
	return tmp | (pop_byte() << 8);
}

// Push a byte onto the stack
inline void MOS6510::push_byte(UBYTE byte)
{
	if (sp == 0)
		stack_underflow = TRUE;
	RAM[(sp--) & 0xff | 0x0100] = byte;
}

// Pop processor flags from the stack
inline void MOS6510::pop_flags(void)
{
	UBYTE flags = pop_byte();

	n_flag = flags;
	v_flag = flags & 0x40;
	d_flag = flags & 0x08;
	i_flag = flags & 0x04;
	z_flag = !(flags & 0x02);
	c_flag = flags & 0x01;
}

// Push processor flags onto the stack
inline void MOS6510::push_flags(bool b_flag)
{
	UBYTE flags = 0x20 | (n_flag & 0x80);

	if (v_flag) flags |= 0x40;
	if (b_flag) flags |= 0x10;
	if (d_flag) flags |= 0x08;
	if (i_flag) flags |= 0x04;
	if (!z_flag) flags |= 0x02;
	if (c_flag) flags |= 0x01;

	push_byte(flags);
}


/*
 *  Set N and Z flags according to byte
 */

#define set_nz(x) (z_flag = n_flag = (x))


/*
 *  Adc instruction
 */

void MOS6510::do_adc(UBYTE byte)
{
	if (!d_flag) {
		UWORD tmp;

		// Binary mode
		tmp = a + byte + (c_flag ? 1 : 0);
		c_flag = tmp > 0xff;
		v_flag = !((a ^ byte) & 0x80) && ((a ^ tmp) & 0x80);
		set_nz(a = tmp);

	} else {
		UWORD al, ah;

		// Decimal mode
		al = (a & 0x0f) + (byte & 0x0f) + (c_flag ? 1 : 0);		// Calculate lower nybble
		if (al > 9) al += 6;									// BCD fixup for lower nybble

		ah = (a >> 4) + (byte >> 4);							// Calculate upper nybble
		if (al > 0x0f) ah++;

		z_flag = (a + byte + (c_flag ? 1 : 0)) & 0xff;			// Set flags
		n_flag = ah << 4;
		v_flag = (((ah << 4) ^ a) & 0x80) && !((a ^ byte) & 0x80);

		if (ah > 9) ah += 6;									// BCD fixup for upper nybble
		c_flag = ah > 0x0f;										// Set carry flag
		a = (ah << 4) | (al & 0x0f);							// Compose result
	}
}


/*
 * Sbc instruction
 */

void MOS6510::do_sbc(UBYTE byte)
{
	UWORD tmp = a - byte - (c_flag ? 0 : 1);

	if (!d_flag) {

		// Binary mode
		c_flag = tmp < 0x100;
		v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80);
		set_nz(a = tmp);

	} else {
		UWORD al, ah;

		// Decimal mode
		al = (a & 0x0f) - (byte & 0x0f) - (c_flag ? 0 : 1);		// Calculate lower nybble
		ah = (a >> 4) - (byte >> 4);							// Calculate upper nybble
		if (al & 0x10) {
			al -= 6;											// BCD fixup for lower nybble
			ah--;
		}
		if (ah & 0x10) ah -= 6;									// BCD fixup for upper nybble

		c_flag = tmp < 0x100;									// Set flags
		v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80);
		set_nz(tmp);

		a = (ah << 4) | (al & 0x0f);							// Compose result
	}
}


/*
 *  Emulate 6510 instructions until stack under-/overflow,
 *  RTI or illegal opcode encountered
 */

void MOS6510::Emulate(UWORD startadr, UBYTE ra, UBYTE rx, UBYTE ry)
{
	UBYTE tmp, tmp2;
	UWORD adr;

	a = ra; x = rx; y = ry;
	sp = 0xff;
	stack_underflow = FALSE;
	n_flag = v_flag = d_flag = i_flag = z_flag = c_flag = 0;
	jump(startadr);

	// Main opcode fetch/execute loop
	do {
		switch (read_byte_imm()) {


		// Load group
		case 0xa9:	// LDA #imm
			set_nz(a = read_byte_imm());
			break;

		case 0xa5:	// LDA zero
			set_nz(a = read_byte_zero());
			break;

		case 0xb5:	// LDA zero,X
			set_nz(a = read_byte_zero_x());
			break;

		case 0xad:	// LDA abs
			set_nz(a = read_byte_abs());
			break;

		case 0xbd:	// LDA abs,X
			set_nz(a = read_byte_abs_x());
			break;

		case 0xb9:	// LDA abs,Y
			set_nz(a = read_byte_abs_y());
			break;

		case 0xa1:	// LDA (ind,X)
			set_nz(a = read_byte_ind_x());
			break;
		
		case 0xb1:	// LDA (ind),Y
			set_nz(a = read_byte_ind_y());
			break;

		case 0xa2:	// LDX #imm
			set_nz(x = read_byte_imm());
			break;

		case 0xa6:	// LDX zero
			set_nz(x = read_byte_zero());
			break;

		case 0xb6:	// LDX zero,Y
			set_nz(x = read_byte_zero_y());
			break;

		case 0xae:	// LDX abs
			set_nz(x = read_byte_abs());
			break;

		case 0xbe:	// LDX abs,Y
			set_nz(x = read_byte_abs_y());
			break;

		case 0xa0:	// LDY #imm
			set_nz(y = read_byte_imm());
			break;

		case 0xa4:	// LDY zero
			set_nz(y = read_byte_zero());
			break;

		case 0xb4:	// LDY zero,X
			set_nz(y = read_byte_zero_x());
			break;

		case 0xac:	// LDY abs
			set_nz(y = read_byte_abs());
			break;

		case 0xbc:	// LDY abs,X
			set_nz(y = read_byte_abs_x());
			break;


		// Store group
		case 0x85:	// STA zero
			write_byte(read_adr_zero(), a);
			break;

		case 0x95:	// STA zero,X
			write_byte(read_adr_zero_x(), a);
			break;

		case 0x8d:	// STA abs
			write_byte(read_adr_abs(), a);
			break;

		case 0x9d:	// STA abs,X
			write_byte(read_adr_abs_x(), a);
			break;

		case 0x99:	// STA abs,Y
			write_byte(read_adr_abs_y(), a);
			break;

		case 0x81:	// STA (ind,X)
			write_byte(read_adr_ind_x(), a);
			break;

		case 0x91:	// STA (ind),Y
			write_byte(read_adr_ind_y(), a);
			break;

		case 0x86:	// STX zero
			write_byte(read_adr_zero(), x);
			break;

		case 0x96:	// STX zero,Y
			write_byte(read_adr_zero_y(), x);
			break;

		case 0x8e:	// STX abs
			write_byte(read_adr_abs(), x);
			break;

		case 0x84:	// STY zero
			write_byte(read_adr_zero(), y);
			break;

		case 0x94:	// STY zero,X
			write_byte(read_adr_zero_x(), y);
			break;

		case 0x8c:	// STY abs
			write_byte(read_adr_abs(), y);
			break;


		// Transfer group
		case 0xaa:	// TAX
			set_nz(x = a);
			break;

		case 0x8a:	// TXA
			set_nz(a = x);
			break;

		case 0xa8:	// TAY
			set_nz(y = a);
			break;

		case 0x98:	// TYA
			set_nz(a = y);
			break;

		case 0xba:	// TSX
			set_nz(x = sp);
			break;

		case 0x9a:	// TXS
			sp = x;
			break;


		// Arithmetic group
		case 0x69:	// ADC #imm
			do_adc(read_byte_imm());
			break;

		case 0x65:	// ADC zero
			do_adc(read_byte_zero());
			break;

		case 0x75:	// ADC zero,X
			do_adc(read_byte_zero_x());
			break;

		case 0x6d:	// ADC abs
			do_adc(read_byte_abs());
			break;

		case 0x7d:	// ADC abs,X
			do_adc(read_byte_abs_x());
			break;

		case 0x79:	// ADC abs,Y
			do_adc(read_byte_abs_y());
			break;

		case 0x61:	// ADC (ind,X)
			do_adc(read_byte_ind_x());
			break;

		case 0x71:	// ADC (ind),Y
			do_adc(read_byte_ind_y());
			break;

		case 0xe9:	// SBC #imm
		case 0xeb:
			do_sbc(read_byte_imm());
			break;

		case 0xe5:	// SBC zero
			do_sbc(read_byte_zero());
			break;

		case 0xf5:	// SBC zero,X
			do_sbc(read_byte_zero_x());
			break;

		case 0xed:	// SBC abs
			do_sbc(read_byte_abs());
			break;

		case 0xfd:	// SBC abs,X
			do_sbc(read_byte_abs_x());
			break;

		case 0xf9:	// SBC abs,Y
			do_sbc(read_byte_abs_y());
			break;

		case 0xe1:	// SBC (ind,X)
			do_sbc(read_byte_ind_x());
			break;

		case 0xf1:	// SBC (ind),Y
			do_sbc(read_byte_ind_y());
			break;


		// Increment/decrement group
		case 0xe8:	// INX
			set_nz(++x);
			break;

		case 0xca:	// DEX
			set_nz(--x);
			break;

		case 0xc8:	// INY
			set_nz(++y);
			break;

		case 0x88:	// DEY
			set_nz(--y);
			break;

		case 0xe6:	// INC zero
			adr = read_adr_zero();
			write_zp(adr, set_nz(read_zp(adr) + 1));
			break;

		case 0xf6:	// INC zero,X
			adr = read_adr_zero_x();
			write_zp(adr, set_nz(read_zp(adr) + 1));
			break;

		case 0xee:	// INC abs
			adr = read_adr_abs();
			write_byte(adr, set_nz(read_byte(adr) + 1));
			break;

		case 0xfe:	// INC abs,X
			adr = read_adr_abs_x();
			write_byte(adr, set_nz(read_byte(adr) + 1));
			break;

		case 0xc6:	// DEC zero
			adr = read_adr_zero();
			write_zp(adr, set_nz(read_zp(adr) - 1));
			break;

		case 0xd6:	// DEC zero,X
			adr = read_adr_zero_x();
			write_zp(adr, set_nz(read_zp(adr) - 1));
			break;

		case 0xce:	// DEC abs
			adr = read_adr_abs();
			write_byte(adr, set_nz(read_byte(adr) - 1));
			break;

		case 0xde:	// DEC abs,X
			adr = read_adr_abs_x();
			write_byte(adr, set_nz(read_byte(adr) - 1));
			break;


		// Logic group
		case 0x29:	// AND #imm
			set_nz(a &= read_byte_imm());
			break;

		case 0x25:	// AND zero
			set_nz(a &= read_byte_zero());
			break;

		case 0x35:	// AND zero,X
			set_nz(a &= read_byte_zero_x());
			break;

		case 0x2d:	// AND abs
			set_nz(a &= read_byte_abs());
			break;

		case 0x3d:	// AND abs,X
			set_nz(a &= read_byte_abs_x());
			break;

		case 0x39:	// AND abs,Y
			set_nz(a &= read_byte_abs_y());
			break;

		case 0x21:	// AND (ind,X)
			set_nz(a &= read_byte_ind_x());
			break;

		case 0x31:	// AND (ind),Y
			set_nz(a &= read_byte_ind_y());
			break;

		case 0x09:	// ORA #imm
			set_nz(a |= read_byte_imm());
			break;

		case 0x05:	// ORA zero
			set_nz(a |= read_byte_zero());
			break;

		case 0x15:	// ORA zero,X
			set_nz(a |= read_byte_zero_x());
			break;

		case 0x0d:	// ORA abs
			set_nz(a |= read_byte_abs());
			break;

		case 0x1d:	// ORA abs,X
			set_nz(a |= read_byte_abs_x());
			break;

		case 0x19:	// ORA abs,Y
			set_nz(a |= read_byte_abs_y());
			break;

		case 0x01:	// ORA (ind,X)
			set_nz(a |= read_byte_ind_x());
			break;

		case 0x11:	// ORA (ind),Y
			set_nz(a |= read_byte_ind_y());
			break;

		case 0x49:	// EOR #imm
			set_nz(a ^= read_byte_imm());
			break;

		case 0x45:	// EOR zero
			set_nz(a ^= read_byte_zero());
			break;

		case 0x55:	// EOR zero,X
			set_nz(a ^= read_byte_zero_x());
			break;

		case 0x4d:	// EOR abs
			set_nz(a ^= read_byte_abs());
			break;

		case 0x5d:	// EOR abs,X
			set_nz(a ^= read_byte_abs_x());
			break;

		case 0x59:	// EOR abs,Y
			set_nz(a ^= read_byte_abs_y());
			break;

		case 0x41:	// EOR (ind,X)
			set_nz(a ^= read_byte_ind_x());
			break;

		case 0x51:	// EOR (ind),Y
			set_nz(a ^= read_byte_ind_y());
			break;


		// Compare group
		case 0xc9:	// CMP #imm
			set_nz(adr = a - read_byte_imm());
			c_flag = adr < 0x100;
			break;

		case 0xc5:	// CMP zero
			set_nz(adr = a - read_byte_zero());
			c_flag = adr < 0x100;
			break;

		case 0xd5:	// CMP zero,X
			set_nz(adr = a - read_byte_zero_x());
			c_flag = adr < 0x100;
			break;

		case 0xcd:	// CMP abs
			set_nz(adr = a - read_byte_abs());
			c_flag = adr < 0x100;
			break;

		case 0xdd:	// CMP abs,X
			set_nz(adr = a - read_byte_abs_x());
			c_flag = adr < 0x100;
			break;

		case 0xd9:	// CMP abs,Y
			set_nz(adr = a - read_byte_abs_y());
			c_flag = adr < 0x100;
			break;

		case 0xc1:	// CMP (ind,X)
			set_nz(adr = a - read_byte_ind_x());
			c_flag = adr < 0x100;
			break;

		case 0xd1:	// CMP (ind),Y
			set_nz(adr = a - read_byte_ind_y());
			c_flag = adr < 0x100;
			break;

		case 0xe0:	// CPX #imm
			set_nz(adr = x - read_byte_imm());
			c_flag = adr < 0x100;
			break;

		case 0xe4:	// CPX zero
			set_nz(adr = x - read_byte_zero());
			c_flag = adr < 0x100;
			break;

		case 0xec:	// CPX abs
			set_nz(adr = x - read_byte_abs());
			c_flag = adr < 0x100;
			break;

		case 0xc0:	// CPY #imm
			set_nz(adr = y - read_byte_imm());
			c_flag = adr < 0x100;
			break;

		case 0xc4:	// CPY zero
			set_nz(adr = y - read_byte_zero());
			c_flag = adr < 0x100;
			break;

		case 0xcc:	// CPY abs
			set_nz(adr = y - read_byte_abs());
			c_flag = adr < 0x100;
			break;


		// Bit-test group
		case 0x24:	// BIT zero
			z_flag = a & (tmp = read_byte_zero());
			n_flag = tmp;
			v_flag = tmp & 0x40;
			break;

		case 0x2c:	// BIT abs
			z_flag = a & (tmp = read_byte_abs());
			n_flag = tmp;
			v_flag = tmp & 0x40;
			break;


		// Shift/rotate group
		case 0x0a:	// ASL A
			c_flag = a & 0x80;
			set_nz(a <<= 1);
			break;

		case 0x06:	// ASL zero
			tmp = read_zp(adr = read_adr_zero());
			c_flag = tmp & 0x80;
			write_zp(adr, set_nz(tmp << 1));
			break;

		case 0x16:	// ASL zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			c_flag = tmp & 0x80;
			write_zp(adr, set_nz(tmp << 1));
			break;

		case 0x0e:	// ASL abs
			tmp = read_byte(adr = read_adr_abs());
			c_flag = tmp & 0x80;
			write_byte(adr, set_nz(tmp << 1));
			break;

		case 0x1e:	// ASL abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			c_flag = tmp & 0x80;
			write_byte(adr, set_nz(tmp << 1));
			break;

		case 0x4a:	// LSR A
			c_flag = a & 0x01;
			set_nz(a >>= 1);
			break;

		case 0x46:	// LSR zero
			tmp = read_zp(adr = read_adr_zero());
			c_flag = tmp & 0x01;
			write_zp(adr, set_nz(tmp >> 1));
			break;

		case 0x56:	// LSR zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			c_flag = tmp & 0x01;
			write_zp(adr, set_nz(tmp >> 1));
			break;

		case 0x4e:	// LSR abs
			tmp = read_byte(adr = read_adr_abs());
			c_flag = tmp & 0x01;
			write_byte(adr, set_nz(tmp >> 1));
			break;

		case 0x5e:	// LSR abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			c_flag = tmp & 0x01;
			write_byte(adr, set_nz(tmp >> 1));
			break;

		case 0x2a:	// ROL A
			tmp2 = a & 0x80;
			set_nz(a = c_flag ? (a << 1) | 0x01 : a << 1);
			c_flag = tmp2;
			break;

		case 0x26:	// ROL zero
			tmp = read_zp(adr = read_adr_zero());
			tmp2 = tmp & 0x80;
			write_zp(adr, set_nz(c_flag ? (tmp << 1) | 0x01 : tmp << 1));
			c_flag = tmp2;
			break;

		case 0x36:	// ROL zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			tmp2 = tmp & 0x80;
			write_zp(adr, set_nz(c_flag ? (tmp << 1) | 0x01 : tmp << 1));
			c_flag = tmp2;
			break;

		case 0x2e:	// ROL abs
			tmp = read_byte(adr = read_adr_abs());
			tmp2 = tmp & 0x80;
			write_byte(adr, set_nz(c_flag ? (tmp << 1) | 0x01 : tmp << 1));
			c_flag = tmp2;
			break;

		case 0x3e:	// ROL abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			tmp2 = tmp & 0x80;
			write_byte(adr, set_nz(c_flag ? (tmp << 1) | 0x01 : tmp << 1));
			c_flag = tmp2;
			break;

		case 0x6a:	// ROR A
			tmp2 = a & 0x01;
			set_nz(a = (c_flag ? (a >> 1) | 0x80 : a >> 1));
			c_flag = tmp2;
			break;

		case 0x66:	// ROR zero
			tmp = read_zp(adr = read_adr_zero());
			tmp2 = tmp & 0x01;
			write_zp(adr, set_nz(c_flag ? (tmp >> 1) | 0x80 : tmp >> 1));
			c_flag = tmp2;
			break;

		case 0x76:	// ROR zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			tmp2 = tmp & 0x01;
			write_zp(adr, set_nz(c_flag ? (tmp >> 1) | 0x80 : tmp >> 1));
			c_flag = tmp2;
			break;

		case 0x6e:	// ROR abs
			tmp = read_byte(adr = read_adr_abs());
			tmp2 = tmp & 0x01;
			write_byte(adr, set_nz(c_flag ? (tmp >> 1) | 0x80 : tmp >> 1));
			c_flag = tmp2;
			break;

		case 0x7e:	// ROR abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			tmp2 = tmp & 0x01;
			write_byte(adr, set_nz(c_flag ? (tmp >> 1) | 0x80 : tmp >> 1));
			c_flag = tmp2;
			break;


		// Stack group
		case 0x48:	// PHA
			push_byte(a);
			break;

		case 0x68:	// PLA
			set_nz(a = pop_byte());
			break;

		case 0x08:	// PHP
			push_flags(TRUE);
			break;

		case 0x28:	// PLP
			pop_flags();
			break;


		// Jump/branch group
		case 0x4c:	// JMP abs
			jump(read_adr_abs());
			break;

		case 0x6c:	// JMP (ind)
			adr = read_adr_abs();
			jump(read_byte(adr) | (read_byte((adr + 1) & 0xff | adr & 0xff00) << 8));
			break;

		case 0x20:	// JSR abs
			push_byte((pc-pc_base+1) >> 8); push_byte(pc-pc_base+1);
			jump(read_adr_abs());
			break;

		case 0x60:	// RTS
			jump(pop_word() + 1);
			break;

		case 0x40:	// RTI
			return;

		case 0x00:	// BRK
			push_byte((pc+1-pc_base) >> 8); push_byte(pc+1-pc_base);
			push_flags(TRUE);
			i_flag = TRUE;
			jump(read_word(0xfffe));
			break;

#define Branch(flag) \
	if (flag) { \
		pc += (BYTE)*pc + 1; \
		break; \
	} else { \
		pc++; \
		break; \
	}

		case 0xb0:	// BCS rel
			Branch(c_flag);

		case 0x90:	// BCC rel
			Branch(!c_flag);

		case 0xf0:	// BEQ rel
			Branch(!z_flag);

		case 0xd0:	// BNE rel
			Branch(z_flag);

		case 0x70:	// BVS rel
			Branch(v_flag);

		case 0x50:	// BVC rel
			Branch(!v_flag);

		case 0x30:	// BMI rel
			Branch(n_flag & 0x80);

		case 0x10:	// BPL rel
			Branch(!(n_flag & 0x80));


		// Flags group
		case 0x38:	// SEC
			c_flag = TRUE;
			break;

		case 0x18:	// CLC
			c_flag = FALSE;
			break;

		case 0xf8:	// SED
			d_flag = TRUE;
			break;

		case 0xd8:	// CLD
			d_flag = FALSE;
			break;

		case 0x78:	// SEI
			i_flag = TRUE;
			break;

		case 0x58:	// CLI
			i_flag = FALSE;
			break;

		case 0xb8:	// CLV
			v_flag = FALSE;
			break;


		// NOP group
		case 0xea:	// NOP
			break;


/*
 * Undocumented opcodes start here
 */

		// NOP group
		case 0x1a:	// NOP
		case 0x3a:
		case 0x5a:
		case 0x7a:
		case 0xda:
		case 0xfa:
			break;

		case 0x80:	// NOP #imm
		case 0x82:
		case 0x89:
		case 0xc2:
		case 0xe2:
		case 0x04:	// NOP zero
		case 0x44:
		case 0x64:
		case 0x14:	// NOP zero,X
		case 0x34:
		case 0x54:
		case 0x74:
		case 0xd4:
		case 0xf4:
			pc++;
			break;

		case 0x0c:	// NOP abs
		case 0x1c:	// NOP abs,X
		case 0x3c:
		case 0x5c:
		case 0x7c:
		case 0xdc:
		case 0xfc:
			pc+=2;
			break;


		// Load A/X group
		case 0xa7:	// LAX zero
			set_nz(a = x = read_byte_zero());
			break;

		case 0xb7:	// LAX zero,Y
			set_nz(a = x = read_byte_zero_y());
			break;

		case 0xaf:	// LAX abs
			set_nz(a = x = read_byte_abs());
			break;

		case 0xbf:	// LAX abs,Y
			set_nz(a = x = read_byte_abs_y());
			break;

		case 0xa3:	// LAX (ind,X)
			set_nz(a = x = read_byte_ind_x());
			break;

		case 0xb3:	// LAX (ind),Y
			set_nz(a = x = read_byte_ind_y());
			break;


		// Store A/X group
		case 0x87:	// SAX zero
			write_byte(read_adr_zero(), a & x);
			break;

		case 0x97:	// SAX zero,Y
			write_byte(read_adr_zero_y(), a & x);
			break;

		case 0x8f:	// SAX abs
			write_byte(read_adr_abs(), a & x);
			break;

		case 0x83:	// SAX (ind,X)
			write_byte(read_adr_ind_x(), a & x);
			break;


		// ASL/ORA group
#define ShiftLeftOr \
	c_flag = tmp & 0x80; \
	tmp <<= 1; \
	set_nz(a |= tmp);

		case 0x07:	// SLO zero
			tmp = read_zp(adr = read_adr_zero());
			ShiftLeftOr;
			write_zp(adr, tmp);
			break;

		case 0x17:	// SLO zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			ShiftLeftOr;
			write_zp(adr, tmp);
			break;

		case 0x0f:	// SLO abs
			tmp = read_byte(adr = read_adr_abs());
			ShiftLeftOr;
			write_byte(adr, tmp);
			break;

		case 0x1f:	// SLO abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			ShiftLeftOr;
			write_byte(adr, tmp);
			break;

		case 0x1b:	// SLO abs,Y
			tmp = read_byte(adr = read_adr_abs_y());
			ShiftLeftOr;
			write_byte(adr, tmp);
			break;

		case 0x03:	// SLO (ind,X)
			tmp = read_byte(adr = read_adr_ind_x());
			ShiftLeftOr;
			write_byte(adr, tmp);
			break;

		case 0x13:	// SLO (ind),Y
			tmp = read_byte(adr = read_adr_ind_y());
			ShiftLeftOr;
			write_byte(adr, tmp);
			break;


		// ROL/AND group
#define RoLeftAnd \
	tmp2 = tmp & 0x80; \
	tmp = c_flag ? (tmp << 1) | 0x01 : tmp << 1; \
	set_nz(a &= tmp); \
	c_flag = tmp2;

		case 0x27:	// RLA zero
			tmp = read_zp(adr = read_adr_zero());
			RoLeftAnd;
			write_zp(adr, tmp);
			break;

		case 0x37:	// RLA zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			RoLeftAnd;
			write_zp(adr, tmp);
			break;

		case 0x2f:	// RLA abs
			tmp = read_byte(adr = read_adr_abs());
			RoLeftAnd;
			write_byte(adr, tmp);
			break;

		case 0x3f:	// RLA abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			RoLeftAnd;
			write_byte(adr, tmp);
			break;

		case 0x3b:	// RLA abs,Y
			tmp = read_byte(adr = read_adr_abs_y());
			RoLeftAnd;
			write_byte(adr, tmp);
			break;

		case 0x23:	// RLA (ind,X)
			tmp = read_byte(adr = read_adr_ind_x());
			RoLeftAnd;
			write_byte(adr, tmp);
			break;

		case 0x33:	// RLA (ind),Y
			tmp = read_byte(adr = read_adr_ind_y());
			RoLeftAnd;
			write_byte(adr, tmp);
			break;


		// LSR/EOR group
#define ShiftRightEor \
	c_flag = tmp & 0x01; \
	tmp >>= 1; \
	set_nz(a ^= tmp);

		case 0x47:	// SRE zero
			tmp = read_zp(adr = read_adr_zero());
			ShiftRightEor;
			write_zp(adr, tmp);
			break;

		case 0x57:	// SRE zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			ShiftRightEor;
			write_zp(adr, tmp);
			break;

		case 0x4f:	// SRE abs
			tmp = read_byte(adr = read_adr_abs());
			ShiftRightEor;
			write_byte(adr, tmp);
			break;

		case 0x5f:	// SRE abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			ShiftRightEor;
			write_byte(adr, tmp);
			break;

		case 0x5b:	// SRE abs,Y
			tmp = read_byte(adr = read_adr_abs_y());
			ShiftRightEor;
			write_byte(adr, tmp);
			break;

		case 0x43:	// SRE (ind,X)
			tmp = read_byte(adr = read_adr_ind_x());
			ShiftRightEor;
			write_byte(adr, tmp);
			break;

		case 0x53:	// SRE (ind),Y
			tmp = read_byte(adr = read_adr_ind_y());
			ShiftRightEor;
			write_byte(adr, tmp);
			break;


		// ROR/ADC group
#define RoRightAdc \
	tmp2 = tmp & 0x01; \
	tmp = c_flag ? (tmp >> 1) | 0x80 : tmp >> 1; \
	c_flag = tmp2; \
	do_adc(tmp);

		case 0x67:	// RRA zero
			tmp = read_zp(adr = read_adr_zero());
			RoRightAdc;
			write_zp(adr, tmp);
			break;

		case 0x77:	// RRA zero,X
			tmp = read_zp(adr = read_adr_zero_x());
			RoRightAdc;
			write_zp(adr, tmp);
			break;

		case 0x6f:	// RRA abs
			tmp = read_byte(adr = read_adr_abs());
			RoRightAdc;
			write_byte(adr, tmp);
			break;

		case 0x7f:	// RRA abs,X
			tmp = read_byte(adr = read_adr_abs_x());
			RoRightAdc;
			write_byte(adr, tmp);
			break;

		case 0x7b:	// RRA abs,Y
			tmp = read_byte(adr = read_adr_abs_y());
			RoRightAdc;
			write_byte(adr, tmp);
			break;

		case 0x63:	// RRA (ind,X)
			tmp = read_byte(adr = read_adr_ind_x());
			RoRightAdc;
			write_byte(adr, tmp);
			break;

		case 0x73:	// RRA (ind),Y
			tmp = read_byte(adr = read_adr_ind_y());
			RoRightAdc;
			write_byte(adr, tmp);
			break;


		// DEC/CMP group
#define DecCompare \
	set_nz(adr = a - tmp); \
	c_flag = adr < 0x100;

		case 0xc7:	// DCP zero
			tmp = read_zp(adr = read_adr_zero()) - 1;
			write_zp(adr, tmp);
			DecCompare;
			break;

		case 0xd7:	// DCP zero,X
			tmp = read_zp(adr = read_adr_zero_x()) - 1;
			write_zp(adr, tmp);
			DecCompare;
			break;

		case 0xcf:	// DCP abs
			tmp = read_byte(adr = read_adr_abs()) - 1;
			write_byte(adr, tmp);
			DecCompare;
			break;

		case 0xdf:	// DCP abs,X
			tmp = read_byte(adr = read_adr_abs_x()) - 1;
			write_byte(adr, tmp);
			DecCompare;
			break;

		case 0xdb:	// DCP abs,Y
			tmp = read_byte(adr = read_adr_abs_y()) - 1;
			write_byte(adr, tmp);
			DecCompare;
			break;

		case 0xc3:	// DCP (ind,X)
			tmp = read_byte(adr = read_adr_ind_x()) - 1;
			write_byte(adr, tmp);
			DecCompare;
			break;

		case 0xd3:	// DCP (ind),Y
			tmp = read_byte(adr = read_adr_ind_y()) - 1;
			write_byte(adr, tmp);
			DecCompare;
			break;


		// INC/SBC group
		case 0xe7:	// ISB zero
			tmp = read_zp(adr = read_adr_zero()) + 1;
			do_sbc(tmp);
			write_zp(adr, tmp);
			break;

		case 0xf7:	// ISB zero,X
			tmp = read_zp(adr = read_adr_zero_x()) + 1;
			do_sbc(tmp);
			write_zp(adr, tmp);
			break;

		case 0xef:	// ISB abs
			tmp = read_byte(adr = read_adr_abs()) + 1;
			do_sbc(tmp);
			write_byte(adr, tmp);
			break;

		case 0xff:	// ISB abs,X
			tmp = read_byte(adr = read_adr_abs_x()) + 1;
			do_sbc(tmp);
			write_byte(adr, tmp);
			break;

		case 0xfb:	// ISB abs,Y
			tmp = read_byte(adr = read_adr_abs_y()) + 1;
			do_sbc(tmp);
			write_byte(adr, tmp);
			break;

		case 0xe3:	// ISB (ind,X)
			tmp = read_byte(adr = read_adr_ind_x()) + 1;
			do_sbc(tmp);
			write_byte(adr, tmp);
			break;

		case 0xf3:	// ISB (ind),Y
			tmp = read_byte(adr = read_adr_ind_y()) + 1;
			do_sbc(tmp);
			write_byte(adr, tmp);
			break;


		// Complex functions
		case 0x0b:	// ANC #imm
		case 0x2b:
			set_nz(a &= read_byte_imm());
			c_flag = n_flag & 0x80;
			break;

		case 0x4b:	// ASR #imm
			a &= read_byte_imm();
			c_flag = a & 0x01;
			set_nz(a >>= 1);
			break;

		case 0x6b:	// ARR #imm
			tmp2 = read_byte_imm() & a;
			a = (c_flag ? (tmp2 >> 1) | 0x80 : tmp2 >> 1);
			if (!d_flag) {
				set_nz(a);
				c_flag = a & 0x40;
				v_flag = (a & 0x40) ^ ((a & 0x20) << 1);
			} else {
				n_flag = c_flag ? 0x80 : 0;
				z_flag = a;
				v_flag = (tmp2 ^ a) & 0x40;
				if ((tmp2 & 0x0f) + (tmp2 & 0x01) > 5)
					a = a & 0xf0 | (a + 6) & 0x0f;
				if (c_flag = ((tmp2 + (tmp2 & 0x10)) & 0x1f0) > 0x50)
					a += 0x60;
			}
			break;

		case 0x8b:	// ANE #imm
			set_nz(a = read_byte_imm() & x & (a | 0xee));
			break;

		case 0x93:	// SHA (ind),Y
			tmp2 = read_zp(pc[0] + 1);
			write_byte(read_adr_ind_y(), a & x & (tmp2+1));
			break;

		case 0x9b:	// SHS abs,Y
			tmp2 = pc[1];
			write_byte(read_adr_abs_y(), a & x & (tmp2+1));
			sp = a & x;
			break;

		case 0x9c:	// SHY abs,X
			tmp2 = pc[1];
			write_byte(read_adr_abs_x(), y & (tmp2+1));
			break;

		case 0x9e:	// SHX abs,Y
			tmp2 = pc[1];
			write_byte(read_adr_abs_y(), x & (tmp2+1));
			break;

		case 0x9f:	// SHA abs,Y
			tmp2 = pc[1];
			write_byte(read_adr_abs_y(), a & x & (tmp2+1));
			break;

		case 0xab:	// LXA #imm
			set_nz(a = x = (a | 0xee) & read_byte_imm());
			break;

		case 0xbb:	// LAS abs,Y
			set_nz(a = x = sp = read_byte_abs_y() & sp);
			break;

		case 0xcb:	// SBX #imm
			x &= a;
			adr = x - read_byte_imm();
			c_flag = adr < 0x100;
			set_nz(x = adr);
			break;

		case 0x02:
		case 0x12:
		case 0x22:
		case 0x32:
		case 0x42:
		case 0x52:
		case 0x62:
		case 0x72:
		case 0x92:
		case 0xb2:
		case 0xd2:
			return;
		}

	} while (!stack_underflow);
}
