raze/libraries/game-music-emu/gme/Hes_Cpu.cpp
Christoph Oelckers 718112a8fe - added external libraries for music format playback and decompression from GZDoom.
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.
2019-09-22 08:59:48 +02:00

1295 lines
24 KiB
C++

// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Hes_Cpu.h"
#include "blargg_endian.h"
//#include "hes_cpu_log.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
// TODO: support T flag, including clearing it at appropriate times?
// all zero-page should really use whatever is at page 1, but that would
// reduce efficiency quite a bit
int const ram_addr = 0x2000;
#define FLUSH_TIME() (void) (s.time = s_time)
#define CACHE_TIME() (void) (s_time = s.time)
#include "hes_cpu_io.h"
#include "blargg_source.h"
#if BLARGG_NONPORTABLE
#define PAGE_OFFSET( addr ) (addr)
#else
#define PAGE_OFFSET( addr ) ((addr) & (page_size - 1))
#endif
// status flags
int const st_n = 0x80;
int const st_v = 0x40;
int const st_t = 0x20;
int const st_b = 0x10;
int const st_d = 0x08;
int const st_i = 0x04;
int const st_z = 0x02;
int const st_c = 0x01;
void Hes_Cpu::reset()
{
check( state == &state_ );
state = &state_;
state_.time = 0;
state_.base = 0;
irq_time_ = future_hes_time;
end_time_ = future_hes_time;
r.status = st_i;
r.sp = 0;
r.pc = 0;
r.a = 0;
r.x = 0;
r.y = 0;
blargg_verify_byte_order();
}
void Hes_Cpu::set_mmr( int reg, int bank )
{
assert( (unsigned) reg <= page_count ); // allow page past end to be set
assert( (unsigned) bank < 0x100 );
mmr [reg] = bank;
uint8_t const* code = CPU_SET_MMR( this, reg, bank );
state->code_map [reg] = code - PAGE_OFFSET( reg << page_shift );
}
#define TIME (s_time + s.base)
#define READ( addr ) CPU_READ( this, (addr), TIME )
#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );}
#define READ_LOW( addr ) (ram [int (addr)])
#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data))
#define READ_PROG( addr ) (s.code_map [(addr) >> page_shift] [PAGE_OFFSET( 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 ))
bool Hes_Cpu::run( hes_time_t end_time )
{
bool illegal_encountered = false;
set_end_time( end_time );
state_t s = this->state_;
this->state = &s;
// even on x86, using s.time in place of s_time was slower
blargg_long s_time = s.time;
// registers
uint_fast16_t pc = r.pc;
uint_fast8_t a = r.a;
uint_fast8_t x = r.x;
uint_fast8_t y = r.y;
uint_fast16_t sp;
SET_SP( r.sp );
#define IS_NEG (nz & 0x8080)
#define CALC_STATUS( out ) do {\
out = status & (st_v | st_d | st_i);\
out |= ((nz >> 8) | nz) & st_n;\
out |= c >> 8 & st_c;\
if ( !(nz & 0xFF) ) out |= st_z;\
} while ( 0 )
#define SET_STATUS( in ) do {\
status = in & (st_v | st_d | st_i);\
nz = in << 8;\
c = nz;\
nz |= ~in & st_z;\
} while ( 0 )
uint_fast8_t status;
uint_fast16_t c; // carry set if (c & 0x100) != 0
uint_fast16_t nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0
{
uint_fast8_t temp = r.status;
SET_STATUS( temp );
}
goto loop;
branch_not_taken:
s_time -= 2;
loop:
#ifndef NDEBUG
{
hes_time_t correct = end_time_;
if ( !(status & st_i) && correct > irq_time_ )
correct = irq_time_;
check( s.base == correct );
/*
static long count;
if ( count == 1844 ) Debugger();
if ( s.base != correct ) debug_printf( "%ld\n", count );
count++;
*/
}
#endif
check( (unsigned) GET_SP() < 0x100 );
check( (unsigned) a < 0x100 );
check( (unsigned) x < 0x100 );
uint8_t const* instr = s.code_map [pc >> page_shift];
uint_fast8_t opcode;
// TODO: eliminate this special case
#if BLARGG_NONPORTABLE
opcode = instr [pc];
pc++;
instr += pc;
#else
instr += PAGE_OFFSET( pc );
opcode = *instr++;
pc++;
#endif
// TODO: each reference lists slightly different timing values, ugh
static uint8_t const clock_table [256] =
{// 0 1 2 3 4 5 6 7 8 9 A B C D E F
1,7,3, 4,6,4,6,7,3,2,2,2,7,5,7,6,// 0
4,7,7, 4,6,4,6,7,2,5,2,2,7,5,7,6,// 1
7,7,3, 4,4,4,6,7,4,2,2,2,5,5,7,6,// 2
4,7,7, 2,4,4,6,7,2,5,2,2,5,5,7,6,// 3
7,7,3, 4,8,4,6,7,3,2,2,2,4,5,7,6,// 4
4,7,7, 5,2,4,6,7,2,5,3,2,2,5,7,6,// 5
7,7,2, 2,4,4,6,7,4,2,2,2,7,5,7,6,// 6
4,7,7,17,4,4,6,7,2,5,4,2,7,5,7,6,// 7
4,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// 8
4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// 9
2,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// A
4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// B
2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// C
4,7,7,17,2,4,6,7,2,5,3,2,2,5,7,6,// D
2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// E
4,7,7,17,2,4,6,7,2,5,4,2,2,5,7,6 // F
}; // 0x00 was 8
uint_fast16_t data;
data = clock_table [opcode];
if ( (s_time += data) >= 0 )
goto possibly_out_of_time;
almost_out_of_time:
data = *instr;
#ifdef HES_CPU_LOG_H
log_cpu( "new", pc - 1, opcode, instr [0], instr [1], instr [2],
instr [3], instr [4], instr [5] );
//log_opcode( opcode );
#endif
switch ( opcode )
{
possibly_out_of_time:
if ( s_time < (int) data )
goto almost_out_of_time;
s_time -= data;
goto out_of_time;
// Macros
#define GET_MSB() (instr [1])
#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB());
#define GET_ADDR() GET_LE16( instr )
// TODO: is the penalty really always added? the original 6502 was much better
//#define PAGE_CROSS_PENALTY( lsb ) (void) (s_time += (lsb) >> 8)
#define PAGE_CROSS_PENALTY( lsb )
// Branch
// TODO: more efficient way to handle negative branch that wraps PC around
#define BRANCH( cond )\
{\
int_fast16_t offset = (int8_t) data;\
pc++;\
if ( !(cond) ) goto branch_not_taken;\
pc = uint16_t (pc + offset);\
goto loop;\
}
case 0xF0: // BEQ
BRANCH( !((uint8_t) nz) );
case 0xD0: // BNE
BRANCH( (uint8_t) nz );
case 0x10: // BPL
BRANCH( !IS_NEG );
case 0x90: // BCC
BRANCH( !(c & 0x100) )
case 0x30: // BMI
BRANCH( IS_NEG )
case 0x50: // BVC
BRANCH( !(status & st_v) )
case 0x70: // BVS
BRANCH( status & st_v )
case 0xB0: // BCS
BRANCH( c & 0x100 )
case 0x80: // BRA
branch_taken:
BRANCH( true );
case 0xFF:
if ( pc == idle_addr + 1 )
goto idle_done;
case 0x0F: // BBRn
case 0x1F:
case 0x2F:
case 0x3F:
case 0x4F:
case 0x5F:
case 0x6F:
case 0x7F:
case 0x8F: // BBSn
case 0x9F:
case 0xAF:
case 0xBF:
case 0xCF:
case 0xDF:
case 0xEF: {
uint_fast16_t t = 0x101 * READ_LOW( data );
t ^= 0xFF;
pc++;
data = GET_MSB();
BRANCH( t & (1 << (opcode >> 4)) )
}
case 0x4C: // JMP abs
pc = GET_ADDR();
goto loop;
case 0x7C: // JMP (ind+X)
data += x;
case 0x6C:{// JMP (ind)
data += 0x100 * GET_MSB();
pc = GET_LE16( &READ_PROG( data ) );
goto loop;
}
// Subroutine
case 0x44: // BSR
WRITE_LOW( 0x100 | (sp - 1), pc >> 8 );
sp = (sp - 2) | 0x100;
WRITE_LOW( sp, pc );
goto branch_taken;
case 0x20: { // JSR
uint_fast16_t temp = pc + 1;
pc = GET_ADDR();
WRITE_LOW( 0x100 | (sp - 1), temp >> 8 );
sp = (sp - 2) | 0x100;
WRITE_LOW( sp, temp );
goto loop;
}
case 0x60: // RTS
pc = 0x100 * READ_LOW( 0x100 | (sp - 0xFF) );
pc += 1 + READ_LOW( sp );
sp = (sp - 0xFE) | 0x100;
goto loop;
case 0x00: // BRK
goto handle_brk;
// Common
case 0xBD:{// LDA abs,X
PAGE_CROSS_PENALTY( data + x );
uint_fast16_t addr = GET_ADDR() + x;
pc += 2;
CPU_READ_FAST( this, addr, TIME, nz );
a = nz;
goto loop;
}
case 0x9D:{// STA abs,X
uint_fast16_t addr = GET_ADDR() + x;
pc += 2;
CPU_WRITE_FAST( this, addr, a, TIME );
goto loop;
}
case 0x95: // STA zp,x
data = uint8_t (data + x);
case 0x85: // STA zp
pc++;
WRITE_LOW( data, a );
goto loop;
case 0xAE:{// LDX abs
uint_fast16_t addr = GET_ADDR();
pc += 2;
CPU_READ_FAST( this, addr, TIME, nz );
x = nz;
goto loop;
}
case 0xA5: // LDA zp
a = nz = READ_LOW( data );
pc++;
goto loop;
// Load/store
{
uint_fast16_t addr;
case 0x91: // STA (ind),Y
addr = 0x100 * READ_LOW( uint8_t (data + 1) );
addr += READ_LOW( data ) + y;
pc++;
goto sta_ptr;
case 0x81: // STA (ind,X)
data = uint8_t (data + x);
case 0x92: // STA (ind)
addr = 0x100 * READ_LOW( uint8_t (data + 1) );
addr += READ_LOW( data );
pc++;
goto sta_ptr;
case 0x99: // STA abs,Y
data += y;
case 0x8D: // STA abs
addr = data + 0x100 * GET_MSB();
pc += 2;
sta_ptr:
CPU_WRITE_FAST( this, addr, a, TIME );
goto loop;
}
{
uint_fast16_t addr;
case 0xA1: // LDA (ind,X)
data = uint8_t (data + x);
case 0xB2: // LDA (ind)
addr = 0x100 * READ_LOW( uint8_t (data + 1) );
addr += READ_LOW( data );
pc++;
goto a_nz_read_addr;
case 0xB1:// LDA (ind),Y
addr = READ_LOW( data ) + y;
PAGE_CROSS_PENALTY( addr );
addr += 0x100 * READ_LOW( (uint8_t) (data + 1) );
pc++;
goto a_nz_read_addr;
case 0xB9: // LDA abs,Y
data += y;
PAGE_CROSS_PENALTY( data );
case 0xAD: // LDA abs
addr = data + 0x100 * GET_MSB();
pc += 2;
a_nz_read_addr:
CPU_READ_FAST( this, addr, TIME, nz );
a = nz;
goto loop;
}
case 0xBE:{// LDX abs,y
PAGE_CROSS_PENALTY( data + y );
uint_fast16_t addr = GET_ADDR() + y;
pc += 2;
FLUSH_TIME();
x = nz = READ( addr );
CACHE_TIME();
goto loop;
}
case 0xB5: // LDA zp,x
a = nz = READ_LOW( uint8_t (data + x) );
pc++;
goto loop;
case 0xA9: // LDA #imm
pc++;
a = data;
nz = data;
goto loop;
// Bit operations
case 0x3C: // BIT abs,x
data += x;
case 0x2C:{// BIT abs
uint_fast16_t addr;
ADD_PAGE( addr );
FLUSH_TIME();
nz = READ( addr );
CACHE_TIME();
goto bit_common;
}
case 0x34: // BIT zp,x
data = uint8_t (data + x);
case 0x24: // BIT zp
data = READ_LOW( data );
case 0x89: // BIT imm
nz = data;
bit_common:
pc++;
status &= ~st_v;
status |= nz & st_v;
if ( nz & a )
goto loop; // Z should be clear, and nz must be non-zero if nz & a is
nz <<= 8; // set Z flag without affecting N flag
goto loop;
{
uint_fast16_t addr;
case 0xB3: // TST abs,x
addr = GET_MSB() + x;
goto tst_abs;
case 0x93: // TST abs
addr = GET_MSB();
tst_abs:
addr += 0x100 * instr [2];
pc++;
FLUSH_TIME();
nz = READ( addr );
CACHE_TIME();
goto tst_common;
}
case 0xA3: // TST zp,x
nz = READ_LOW( uint8_t (GET_MSB() + x) );
goto tst_common;
case 0x83: // TST zp
nz = READ_LOW( GET_MSB() );
tst_common:
pc += 2;
status &= ~st_v;
status |= nz & st_v;
if ( nz & data )
goto loop; // Z should be clear, and nz must be non-zero if nz & data is
nz <<= 8; // set Z flag without affecting N flag
goto loop;
{
uint_fast16_t addr;
case 0x0C: // TSB abs
case 0x1C: // TRB abs
addr = GET_ADDR();
pc++;
goto txb_addr;
// TODO: everyone lists different behaviors for the status flags, ugh
case 0x04: // TSB zp
case 0x14: // TRB zp
addr = data + ram_addr;
txb_addr:
FLUSH_TIME();
nz = a | READ( addr );
if ( opcode & 0x10 )
nz ^= a; // bits from a will already be set, so this clears them
status &= ~st_v;
status |= nz & st_v;
pc++;
WRITE( addr, nz );
CACHE_TIME();
goto loop;
}
case 0x07: // RMBn
case 0x17:
case 0x27:
case 0x37:
case 0x47:
case 0x57:
case 0x67:
case 0x77:
pc++;
READ_LOW( data ) &= ~(1 << (opcode >> 4));
goto loop;
case 0x87: // SMBn
case 0x97:
case 0xA7:
case 0xB7:
case 0xC7:
case 0xD7:
case 0xE7:
case 0xF7:
pc++;
READ_LOW( data ) |= 1 << ((opcode >> 4) - 8);
goto loop;
// Load/store
case 0x9E: // STZ abs,x
data += x;
case 0x9C: // STZ abs
ADD_PAGE( data );
pc++;
FLUSH_TIME();
WRITE( data, 0 );
CACHE_TIME();
goto loop;
case 0x74: // STZ zp,x
data = uint8_t (data + x);
case 0x64: // STZ zp
pc++;
WRITE_LOW( data, 0 );
goto loop;
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 0xBC: // LDY abs,X
data += x;
PAGE_CROSS_PENALTY( data );
case 0xAC:{// LDY abs
uint_fast16_t addr = data + 0x100 * GET_MSB();
pc += 2;
FLUSH_TIME();
y = nz = READ( addr );
CACHE_TIME();
goto loop;
}
{
uint_fast8_t temp;
case 0x8C: // STY abs
temp = y;
goto store_abs;
case 0x8E: // STX abs
temp = x;
store_abs:
uint_fast16_t addr = GET_ADDR();
pc += 2;
FLUSH_TIME();
WRITE( addr, temp );
CACHE_TIME();
goto loop;
}
// Compare
case 0xEC:{// CPX abs
uint_fast16_t addr = GET_ADDR();
pc++;
FLUSH_TIME();
data = READ( addr );
CACHE_TIME();
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
uint_fast16_t addr = GET_ADDR();
pc++;
FLUSH_TIME();
data = READ( addr );
CACHE_TIME();
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
#define ARITH_ADDR_MODES( op )\
case op - 0x04: /* (ind,x) */\
data = uint8_t (data + x);\
case op + 0x0D: /* (ind) */\
data = 0x100 * READ_LOW( uint8_t (data + 1) ) + READ_LOW( data );\
goto ptr##op;\
case op + 0x0C:{/* (ind),y */\
uint_fast16_t temp = READ_LOW( data ) + y;\
PAGE_CROSS_PENALTY( temp );\
data = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\
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:\
PAGE_CROSS_PENALTY( data );\
case op + 0x08: /* abs */\
ADD_PAGE( data );\
ptr##op:\
FLUSH_TIME();\
data = READ( data );\
CACHE_TIME();\
case op + 0x04: /* imm */\
imm##op:
ARITH_ADDR_MODES( 0xC5 ) // CMP
nz = a - data;
pc++;
c = ~nz;
nz &= 0xFF;
goto loop;
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;
// Add/subtract
ARITH_ADDR_MODES( 0xE5 ) // SBC
data ^= 0xFF;
goto adc_imm;
ARITH_ADDR_MODES( 0x65 ) // ADC
adc_imm: {
if ( status & st_d )
debug_printf( "Decimal mode not supported\n" );
int_fast16_t carry = c >> 8 & 1;
int_fast16_t ov = (a ^ 0x80) + carry + (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;
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_fast16_t temp = c >> 8 & 1;
c = nz;
nz |= temp;
a = (uint8_t) nz;
goto loop;
}
case 0x5E: // LSR abs,X
data += x;
case 0x4E: // LSR abs
c = 0;
case 0x6E: // ROR abs
ror_abs: {
ADD_PAGE( data );
FLUSH_TIME();
int temp = READ( data );
nz = (c >> 1 & 0x80) | (temp >> 1);
c = temp << 8;
goto rotate_common;
}
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:
ADD_PAGE( data );
nz = c >> 8 & 1;
FLUSH_TIME();
nz |= (c = READ( data ) << 1);
rotate_common:
pc++;
WRITE( data, (uint8_t) nz );
CACHE_TIME();
goto loop;
case 0x7E: // ROR abs,X
data += x;
goto ror_abs;
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
#define INC_DEC_AXY( reg, n ) reg = uint8_t (nz = reg + n); goto loop;
case 0x1A: // INA
INC_DEC_AXY( a, +1 )
case 0xE8: // INX
INC_DEC_AXY( x, +1 )
case 0xC8: // INY
INC_DEC_AXY( y, +1 )
case 0x3A: // DEA
INC_DEC_AXY( a, -1 )
case 0xCA: // DEX
INC_DEC_AXY( x, -1 )
case 0x88: // DEY
INC_DEC_AXY( y, -1 )
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 = (unsigned) -1;
add_nz_zp:
nz += READ_LOW( data );
write_nz_zp:
pc++;
WRITE_LOW( data, nz );
goto loop;
case 0xFE: // INC abs,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
data = x + GET_ADDR();
goto dec_ptr;
case 0xCE: // DEC abs
data = GET_ADDR();
dec_ptr:
nz = (unsigned) -1;
inc_common:
FLUSH_TIME();
nz += READ( data );
pc += 2;
WRITE( data, (uint8_t) nz );
CACHE_TIME();
goto loop;
// Transfer
case 0xA8: // TAY
y = a;
nz = a;
goto loop;
case 0x98: // TYA
a = y;
nz = y;
goto loop;
case 0xAA: // TAX
x = a;
nz = a;
goto loop;
case 0x8A: // TXA
a = x;
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;
#define SWAP_REGS( r1, r2 ) {\
uint_fast8_t t = r1;\
r1 = r2;\
r2 = t;\
goto loop;\
}
case 0x02: // SXY
SWAP_REGS( x, y );
case 0x22: // SAX
SWAP_REGS( a, x );
case 0x42: // SAY
SWAP_REGS( a, y );
case 0x62: // CLA
a = 0;
goto loop;
case 0x82: // CLX
x = 0;
goto loop;
case 0xC2: // CLY
y = 0;
goto loop;
// Stack
case 0x48: // PHA
PUSH( a );
goto loop;
case 0xDA: // PHX
PUSH( x );
goto loop;
case 0x5A: // PHY
PUSH( y );
goto loop;
case 0x40:{// RTI
uint_fast8_t 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 );
this->r.status = status; // update externally-visible I flag
if ( (data ^ status) & st_i )
{
hes_time_t new_time = end_time_;
if ( !(status & st_i) && new_time > irq_time_ )
new_time = irq_time_;
blargg_long delta = s.base - new_time;
s.base = new_time;
s_time += delta;
}
goto loop;
}
#define POP() READ_LOW( sp ); sp = (sp - 0xFF) | 0x100
case 0x68: // PLA
a = nz = POP();
goto loop;
case 0xFA: // PLX
x = nz = POP();
goto loop;
case 0x7A: // PLY
y = nz = POP();
goto loop;
case 0x28:{// PLP
uint_fast8_t temp = POP();
uint_fast8_t changed = status ^ temp;
SET_STATUS( temp );
if ( !(changed & st_i) )
goto loop; // I flag didn't change
if ( status & st_i )
goto handle_sei;
goto handle_cli;
}
#undef POP
case 0x08: { // PHP
uint_fast8_t temp;
CALC_STATUS( temp );
PUSH( temp | st_b );
goto loop;
}
// Flags
case 0x38: // SEC
c = (unsigned) ~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: {
this->r.status = status; // update externally-visible I flag
blargg_long delta = s.base - irq_time_;
if ( delta <= 0 )
{
if ( TIME < irq_time_ )
goto loop;
goto delayed_cli;
}
s.base = irq_time_;
s_time += delta;
if ( s_time < 0 )
goto loop;
if ( delta >= s_time + 1 )
{
// delayed irq until after next instruction
s.base += s_time + 1;
s_time = -1;
irq_time_ = s.base; // TODO: remove, as only to satisfy debug check in loop
goto loop;
}
delayed_cli:
debug_printf( "Delayed CLI not supported\n" ); // TODO: implement
goto loop;
}
case 0x78: // SEI
if ( status & st_i )
goto loop;
status |= st_i;
handle_sei: {
this->r.status = status; // update externally-visible I flag
blargg_long delta = s.base - end_time_;
s.base = end_time_;
s_time += delta;
if ( s_time < 0 )
goto loop;
debug_printf( "Delayed SEI not supported\n" ); // TODO: implement
goto loop;
}
// Special
case 0x53:{// TAM
uint_fast8_t const bits = data; // avoid using data across function call
pc++;
for ( int i = 0; i < 8; i++ )
if ( bits & (1 << i) )
set_mmr( i, a );
goto loop;
}
case 0x43:{// TMA
pc++;
byte const* in = mmr;
do
{
if ( data & 1 )
a = *in;
in++;
}
while ( (data >>= 1) != 0 );
goto loop;
}
case 0x03: // ST0
case 0x13: // ST1
case 0x23:{// ST2
uint_fast16_t addr = opcode >> 4;
if ( addr )
addr++;
pc++;
FLUSH_TIME();
CPU_WRITE_VDP( this, addr, data, TIME );
CACHE_TIME();
goto loop;
}
case 0xEA: // NOP
goto loop;
case 0x54: // CSL
debug_printf( "CSL not supported\n" );
illegal_encountered = true;
goto loop;
case 0xD4: // CSH
goto loop;
case 0xF4: { // SET
//fuint16 operand = GET_MSB();
debug_printf( "SET not handled\n" );
//switch ( data )
//{
//}
illegal_encountered = true;
goto loop;
}
// Block transfer
{
uint_fast16_t in_alt;
int_fast16_t in_inc;
uint_fast16_t out_alt;
int_fast16_t out_inc;
case 0xE3: // TIA
in_alt = 0;
goto bxfer_alt;
case 0xF3: // TAI
in_alt = 1;
bxfer_alt:
in_inc = in_alt ^ 1;
out_alt = in_inc;
out_inc = in_alt;
goto bxfer;
case 0xD3: // TIN
in_inc = 1;
out_inc = 0;
goto bxfer_no_alt;
case 0xC3: // TDD
in_inc = -1;
out_inc = -1;
goto bxfer_no_alt;
case 0x73: // TII
in_inc = 1;
out_inc = 1;
bxfer_no_alt:
in_alt = 0;
out_alt = 0;
bxfer:
uint_fast16_t in = GET_LE16( instr + 0 );
uint_fast16_t out = GET_LE16( instr + 2 );
int count = GET_LE16( instr + 4 );
if ( !count )
count = 0x10000;
pc += 6;
WRITE_LOW( 0x100 | (sp - 1), y );
WRITE_LOW( 0x100 | (sp - 2), a );
WRITE_LOW( 0x100 | (sp - 3), x );
FLUSH_TIME();
do
{
// TODO: reads from $0800-$1400 in I/O page return 0 and don't access I/O
uint_fast8_t t = READ( in );
in += in_inc;
in &= 0xFFFF;
s.time += 6;
if ( in_alt )
in_inc = -in_inc;
WRITE( out, t );
out += out_inc;
out &= 0xFFFF;
if ( out_alt )
out_inc = -out_inc;
}
while ( --count );
CACHE_TIME();
goto loop;
}
// Illegal
default:
debug_printf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 );
illegal_encountered = true;
goto loop;
}
assert( false );
int result_;
handle_brk:
pc++;
result_ = 6;
interrupt:
{
s_time += 7;
WRITE_LOW( 0x100 | (sp - 1), pc >> 8 );
WRITE_LOW( 0x100 | (sp - 2), pc );
pc = GET_LE16( &READ_PROG( 0xFFF0 ) + result_ );
sp = (sp - 3) | 0x100;
uint_fast8_t temp;
CALC_STATUS( temp );
if ( result_ == 6 )
temp |= st_b;
WRITE_LOW( sp, temp );
status &= ~st_d;
status |= st_i;
this->r.status = status; // update externally-visible I flag
blargg_long delta = s.base - end_time_;
s.base = end_time_;
s_time += delta;
goto loop;
}
idle_done:
s_time = 0;
out_of_time:
pc--;
FLUSH_TIME();
CPU_DONE( this, TIME, result_ );
CACHE_TIME();
if ( result_ > 0 )
goto interrupt;
if ( s_time < 0 )
goto loop;
s.time = s_time;
r.pc = pc;
r.sp = GET_SP();
r.a = a;
r.x = x;
r.y = y;
{
uint_fast8_t temp;
CALC_STATUS( temp );
r.status = temp;
}
this->state_ = s;
this->state = &this->state_;
return illegal_encountered;
}