forked from Apple-2-HW/arduino-appleii
529 lines
15 KiB
C++
529 lines
15 KiB
C++
// μ6502 - Barebones 6502 Emulator By Damian Peckett
|
|
// dpeckett.com, <damian@pecke.tt>
|
|
|
|
// Address Modes
|
|
#define AD_IMP 0x01
|
|
#define AD_A 0x02
|
|
#define AD_ABS 0x03
|
|
#define AD_ABSX 0x04
|
|
#define AD_ABSY 0x05
|
|
#define AD_IMM 0x06
|
|
#define AD_IND 0x07
|
|
#define AD_INDX 0x08
|
|
#define AD_INDY 0x09
|
|
#define AD_REL 0x0A
|
|
#define AD_ZPG 0x0B
|
|
#define AD_ZPGX 0x0C
|
|
#define AD_ZPGY 0x0D
|
|
|
|
// SR Flag Modes
|
|
#define FL_NONE 0x00
|
|
#define FL_Z 0x20
|
|
#define FL_ZN 0xA0
|
|
#define FL_ZNC 0xB0
|
|
#define FL_ZC 0x30
|
|
#define FL_ALL 0xF0
|
|
|
|
//Unimplemented ops
|
|
#define UNDF 0x00
|
|
|
|
//Other constants
|
|
#define SR_FIXED_BITS 0x20
|
|
#define SR_CARRY 0x01
|
|
#define SR_ZERO 0x02
|
|
#define SR_INT 0x04
|
|
#define SR_DEC 0x08
|
|
#define SR_BRK 0x10
|
|
#define SR_OVER 0x40
|
|
#define SR_NEG 0x80
|
|
|
|
//Stack pointer base address
|
|
#define STP_BASE 0x100
|
|
|
|
//high nibble SR flags, low nibble address mode
|
|
const unsigned char flags[] PROGMEM = {
|
|
AD_IMP, AD_INDX, UNDF, UNDF, UNDF, FL_ZN|AD_ZPG, FL_ZNC|AD_ZPG, UNDF, AD_IMP, FL_ZN|AD_IMM, FL_ZNC|AD_A, UNDF, UNDF, FL_ZN|AD_ABS, FL_ZNC|AD_ABS, UNDF,
|
|
AD_REL, FL_ZN|AD_INDY, UNDF, UNDF, UNDF, FL_ZN|AD_ZPGX, FL_ZNC|AD_ZPGX, UNDF, AD_IMP, FL_ZN|AD_ABSY, UNDF, UNDF, UNDF, FL_ZN|AD_ABSX, FL_ZNC|AD_ABSX, UNDF,
|
|
AD_ABS, FL_ZN|AD_INDX, UNDF, UNDF, FL_Z|AD_ZPG, FL_ZN|AD_ZPG, FL_ZNC|AD_ZPG, UNDF, AD_IMP, FL_ZN|AD_IMM, FL_ZNC|AD_A, UNDF, FL_Z|AD_ABS, FL_ZN|AD_ABS, FL_ZNC|AD_ABS, UNDF,
|
|
AD_REL, FL_ZN|AD_INDY, UNDF, UNDF, UNDF, FL_ZN|AD_ZPGX, FL_ZNC|AD_ZPGX, UNDF, AD_IMP, FL_ZN|AD_ABSY, UNDF, UNDF, UNDF, FL_ZN|AD_ABSX, FL_ZNC|AD_ABSX, UNDF,
|
|
AD_IMP, FL_ZN|AD_INDX, UNDF, UNDF, UNDF, FL_ZN|AD_ZPG, FL_ZNC|AD_ZPG, UNDF, AD_IMP, FL_ZN|AD_IMM, FL_ZNC|AD_A, UNDF, AD_ABS, FL_ZN|AD_ABS, FL_ZNC|AD_ABS, UNDF,
|
|
AD_REL, FL_ZN|AD_INDY, UNDF, UNDF, UNDF, FL_ZN|AD_ZPGX, FL_ZNC|AD_ZPGX, UNDF, AD_IMP, FL_ZN|AD_ABSY, UNDF, UNDF, UNDF, FL_ZN|AD_ABSX, FL_ZNC|AD_ABSX, UNDF,
|
|
AD_IMP, FL_ALL|AD_INDX, UNDF, UNDF, UNDF, FL_ALL|AD_ZPG, FL_ZNC|AD_ZPG, UNDF, FL_ZN|AD_IMP, FL_ALL|AD_IMM, FL_ZNC|AD_A,UNDF, AD_IND, FL_ALL|AD_ABS, FL_ZNC|AD_ABS, UNDF,
|
|
AD_REL, FL_ALL|AD_INDY, UNDF, UNDF, UNDF, FL_ALL|AD_ZPGX, FL_ZNC|AD_ZPGX, UNDF, AD_IMP, FL_ALL|AD_ABSY, UNDF, UNDF, UNDF, FL_ALL|AD_ABSX, FL_ZNC|AD_ABSX, UNDF,
|
|
UNDF, AD_INDX, UNDF, UNDF, AD_ZPG, AD_ZPG, AD_ZPG, UNDF, FL_ZN|AD_IMP, UNDF, FL_ZN|AD_IMP, UNDF, AD_ABS, AD_ABS, AD_ABS, UNDF,
|
|
AD_REL, AD_INDY, UNDF, UNDF, AD_ZPGX, AD_ZPGX, AD_ZPGY, UNDF, FL_ZN|AD_IMP, AD_ABSY, AD_IMP, UNDF, UNDF, AD_ABSX, UNDF, UNDF,
|
|
FL_ZN|AD_IMM, FL_ZN|AD_INDX, FL_ZN|AD_IMM, UNDF, FL_ZN|AD_ZPG, FL_ZN|AD_ZPG, FL_ZN|AD_ZPG, UNDF, FL_ZN|AD_IMP, FL_ZN|AD_IMM, FL_ZN|AD_IMP, UNDF, FL_ZN|AD_ABS, FL_ZN|AD_ABS, FL_ZN|AD_ABS, UNDF,
|
|
AD_REL, FL_ZN|AD_INDY, UNDF, UNDF, FL_ZN|AD_ZPGX, FL_ZN|AD_ZPGX, FL_ZN|AD_ZPGY, UNDF, AD_IMP, FL_ZN|AD_ABSY, FL_ZN|AD_IMP, UNDF, FL_ZN|AD_ABSX, FL_ZN|AD_ABSX, FL_ZN|AD_ABSY, UNDF,
|
|
FL_ZNC|AD_IMM, FL_ZNC|AD_INDX, UNDF, UNDF, FL_ZNC|AD_ZPG, FL_ZNC|AD_ZPG, FL_ZN|AD_ZPG, UNDF, FL_ZN|AD_IMP, FL_ZNC|AD_IMM, FL_ZN|AD_IMP, UNDF, FL_ZNC|AD_ABS, FL_ZNC|AD_ABS, FL_ZN|AD_ABS, UNDF,
|
|
AD_REL, FL_ZNC|AD_INDY, UNDF, UNDF, UNDF, FL_ZNC|AD_ZPGX, FL_ZN|AD_ZPGX, UNDF, AD_IMP, FL_ZNC|AD_ABSY, UNDF, UNDF, UNDF, FL_ZNC|AD_ABSX, FL_ZN|AD_ABSX, UNDF,
|
|
FL_ZNC|AD_IMM, FL_ALL|AD_INDX, UNDF, UNDF, FL_ZNC|AD_ZPG, FL_ALL|AD_ZPG, FL_ZN|AD_ZPG, UNDF, FL_ZN|AD_IMP, FL_ALL|AD_IMM, AD_IMP, UNDF, FL_ZNC|AD_ABS, FL_ALL|AD_ABS, FL_ZN|AD_ABS, UNDF,
|
|
AD_REL, FL_ALL|AD_INDY, UNDF, UNDF, UNDF, FL_ALL|AD_ZPGX, FL_ZN|AD_ZPGX, UNDF, AD_IMP, FL_ALL|AD_ABSY, UNDF, UNDF, UNDF, FL_ALL|AD_ABSX, FL_ZN|AD_ABSX, UNDF
|
|
};
|
|
|
|
// CPU registers
|
|
unsigned short PC;
|
|
unsigned char STP = 0xFD, A = 0x00, X = 0x00, Y = 0x00, SR = SR_FIXED_BITS;
|
|
|
|
//Execution variables
|
|
unsigned char opcode, opflags;
|
|
unsigned short argument_addr;
|
|
|
|
//Temporary variables for flag generation
|
|
unsigned char value8;
|
|
unsigned short value16, value16_2, result;
|
|
|
|
void setflags() {
|
|
// Mask out affected flags
|
|
switch(opflags&0xF0) {
|
|
case 0xA0: SR&=0x7D; break;
|
|
case 0xB0: SR&=0x7C; break;
|
|
case 0x30: SR&=0xFC; break;
|
|
case 0xF0: SR&=0x3C; break;
|
|
case 0x20: SR&=0xFD; break;
|
|
}
|
|
|
|
// Set various status flags
|
|
if(opflags&0x80) SR |= (result&0x0080); //negative
|
|
if(opflags&0x20) SR |= (((result&0xFF) == 0)?0x02:0); //zero
|
|
if(opflags&0x10) SR |= ((result&0xFF00)?0x01:0); //carry
|
|
if(opflags&0x40) SR |= ((result^((unsigned short)A))&(result^value16)&0x0080)>>1;
|
|
}
|
|
|
|
// Stack functions
|
|
void push16(unsigned short pushval) {
|
|
write8(STP_BASE + (STP--), (pushval>>8)&0xFF);
|
|
write8(STP_BASE + (STP--), pushval&0xFF);
|
|
}
|
|
|
|
void push8(unsigned char pushval) {
|
|
write8(STP_BASE + (STP--), pushval);
|
|
}
|
|
|
|
unsigned short pull16() {
|
|
value16 = read8(STP_BASE + (++STP)) | ((unsigned short)read8(STP_BASE + (++STP))<< 8);
|
|
return value16;
|
|
}
|
|
|
|
unsigned char pull8() {
|
|
return read8(STP_BASE + (++STP));
|
|
}
|
|
|
|
void run() {
|
|
// Load the reset vector
|
|
PC = read16(0xFFFC);
|
|
STP = 0xFD;
|
|
|
|
for(;;) {
|
|
// Routines for hooking apple ][ monitor routines
|
|
program_hooks(PC);
|
|
|
|
// Get opcode / addressing mode
|
|
opcode = read8(PC++);
|
|
opflags = pgm_read_byte_near(flags+opcode);
|
|
|
|
// Addressing modes
|
|
switch(opflags&0x0F) {
|
|
case AD_IMP: case AD_A: argument_addr = 0xFFFF; break;
|
|
case AD_ABS:
|
|
argument_addr = read16(PC);
|
|
PC += 2;
|
|
break;
|
|
case AD_ABSX:
|
|
argument_addr = read16(PC) + (unsigned short)X;
|
|
PC += 2;
|
|
break;
|
|
case AD_ABSY:
|
|
argument_addr = read16(PC) + (unsigned short)Y;
|
|
PC += 2;
|
|
break;
|
|
case AD_IMM:
|
|
argument_addr = PC++;
|
|
break;
|
|
case AD_IND:
|
|
argument_addr = read16(PC);
|
|
value16 = (argument_addr&0xFF00) | ((argument_addr+1)&0x00FF); // Page wrap
|
|
argument_addr = (unsigned short)read8(argument_addr) | ((unsigned short)read8(value16) << 8);
|
|
PC+=2;
|
|
break;
|
|
case AD_INDX:
|
|
argument_addr = ((unsigned short)read8(PC++) + (unsigned short)X)&0xFF;
|
|
value16 = (argument_addr&0xFF00) | ((argument_addr+1)&0x00FF); // Page wrap
|
|
argument_addr = (unsigned short)read8(argument_addr) | ((unsigned short)read8(value16) << 8);
|
|
break;
|
|
case AD_INDY:
|
|
argument_addr = (unsigned short)read8(PC++);
|
|
value16 = (argument_addr&0xFF00) | ((argument_addr+1)&0x00FF); // Page wrap
|
|
argument_addr = (unsigned short)read8(argument_addr) | ((unsigned short)read8(value16) << 8);
|
|
argument_addr += Y;
|
|
break;
|
|
case AD_REL:
|
|
argument_addr = (unsigned short)read8(PC++);
|
|
argument_addr |= ((argument_addr&0x80)?0xFF00:0);
|
|
break;
|
|
case AD_ZPG:
|
|
argument_addr = (unsigned short)read8(PC++);
|
|
break;
|
|
case AD_ZPGX:
|
|
argument_addr = ((unsigned short)read8(PC++) + (unsigned short)X)&0xFF;
|
|
break;
|
|
case AD_ZPGY:
|
|
argument_addr = ((unsigned short)read8(PC++) + (unsigned short)Y)&0xFF;
|
|
break;
|
|
}
|
|
|
|
//opcodes
|
|
switch(opcode) {
|
|
//ADC
|
|
case 0x69: case 0x65: case 0x75:
|
|
case 0x6D: case 0x7D: case 0x79:
|
|
case 0x61: case 0x71:
|
|
value16 = (unsigned short)read8(argument_addr);
|
|
result = (unsigned short)A + value16 + (unsigned short)(SR&SR_CARRY);
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//AND
|
|
case 0x29: case 0x25: case 0x35:
|
|
case 0x2D: case 0x3D: case 0x39:
|
|
case 0x21: case 0x31:
|
|
result = A&read8(argument_addr);
|
|
A = result&0xFF;
|
|
setflags();
|
|
break;
|
|
//ASL A
|
|
case 0x0A:
|
|
value16 = (unsigned short)A;
|
|
result = value16<<1;
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//ASL
|
|
case 0x06: case 0x16: case 0x0E:
|
|
case 0x1E:
|
|
value16 = read8(argument_addr);
|
|
result = value16<<1;
|
|
setflags();
|
|
write8(argument_addr, result&0xFF);
|
|
break;
|
|
//BCC
|
|
case 0x90:
|
|
if(!(SR&SR_CARRY)) PC += argument_addr;
|
|
break;
|
|
//BCS
|
|
case 0xB0:
|
|
if((SR&SR_CARRY)) PC += argument_addr;
|
|
break;
|
|
//BEQ
|
|
case 0xF0:
|
|
if((SR&SR_ZERO)) PC += argument_addr;
|
|
break;
|
|
//BNE
|
|
case 0xD0:
|
|
if(!(SR&SR_ZERO)) PC += argument_addr;
|
|
break;
|
|
//BIT
|
|
case 0x24: case 0x2C:
|
|
value8 = read8(argument_addr);
|
|
result = A & value8;
|
|
setflags();
|
|
SR = (SR&0x3F) | (value8&0xC0);
|
|
break;
|
|
//BMI
|
|
case 0x30:
|
|
if((SR&SR_NEG)) PC += argument_addr;
|
|
break;
|
|
//BPL
|
|
case 0x10:
|
|
if(!(SR&SR_NEG)) PC += argument_addr;
|
|
break;
|
|
//BRK
|
|
case 0x00:
|
|
PC++;
|
|
push16(PC);
|
|
push8(SR|SR_BRK);
|
|
SR|=SR_INT;
|
|
PC = read16(0xFFFE);
|
|
break;
|
|
//BVC
|
|
case 0x50:
|
|
if(!(SR&SR_OVER)) PC += argument_addr;
|
|
break;
|
|
//BVS
|
|
case 0x70:
|
|
if(SR&SR_OVER) PC += argument_addr;
|
|
break;
|
|
//CLC
|
|
case 0x18:
|
|
SR&=0xFE;
|
|
break;
|
|
//CLD
|
|
case 0xD8:
|
|
SR&=0xF7;
|
|
break;
|
|
//CLI
|
|
case 0x58:
|
|
SR&=0xFB;
|
|
break;
|
|
//CLV
|
|
case 0xB8:
|
|
SR&=0xBF;
|
|
break;
|
|
//CMP
|
|
case 0xC9: case 0xC5: case 0xD5:
|
|
case 0xCD: case 0xDD: case 0xD9:
|
|
case 0xC1: case 0xD1:
|
|
value16 = ((unsigned short)read8(argument_addr)) ^ 0x00FF;
|
|
result = (unsigned short)A + value16 + (unsigned short)1;
|
|
setflags();
|
|
break;
|
|
//CPX
|
|
case 0xE0: case 0xE4: case 0xEC:
|
|
value16 = ((unsigned short)read8(argument_addr)) ^ 0x00FF;
|
|
result = (unsigned short)X + value16 + (unsigned short)1;
|
|
setflags();
|
|
break;
|
|
//CPY
|
|
case 0xC0: case 0xC4: case 0xCC:
|
|
value16 = ((unsigned short)read8(argument_addr)) ^ 0x00FF;
|
|
result = (unsigned short)Y + value16 + (unsigned short)1;
|
|
setflags();
|
|
break;
|
|
//DEC
|
|
case 0xC6: case 0xD6: case 0xCE:
|
|
case 0xDE:
|
|
value16 = (unsigned short)read8(argument_addr);
|
|
result = value16 - 1;
|
|
setflags();
|
|
write8(argument_addr, result&0xFF);
|
|
break;
|
|
//DEX
|
|
case 0xCA:
|
|
result = --X;
|
|
setflags();
|
|
break;
|
|
//DEY
|
|
case 0x88:
|
|
result = --Y;
|
|
setflags();
|
|
break;
|
|
//EOR
|
|
case 0x49: case 0x45: case 0x55:
|
|
case 0x4D: case 0x5D: case 0x59:
|
|
case 0x41: case 0x51:
|
|
value8 = read8(argument_addr);
|
|
result = A^value8;
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//INC
|
|
case 0xE6: case 0xF6: case 0xEE:
|
|
case 0xFE:
|
|
value16 = (unsigned short)read8(argument_addr);
|
|
result = value16 + 1;
|
|
setflags();
|
|
write8(argument_addr, result&0xFF);
|
|
break;
|
|
//INX
|
|
case 0xE8:
|
|
result = ++X;
|
|
setflags();
|
|
break;
|
|
//INY
|
|
case 0xC8:
|
|
result = ++Y;
|
|
setflags();
|
|
break;
|
|
//JMP
|
|
case 0x4C: case 0x6C:
|
|
PC = argument_addr;
|
|
break;
|
|
//JSR
|
|
case 0x20:
|
|
push16(PC-1);
|
|
PC = argument_addr;
|
|
break;
|
|
//LDA
|
|
case 0xA9: case 0xA5: case 0xB5:
|
|
case 0xAD: case 0xBD: case 0xB9:
|
|
case 0xA1: case 0xB1:
|
|
A = read8(argument_addr);
|
|
result = A;
|
|
setflags();
|
|
break;
|
|
//LDX
|
|
case 0xA2: case 0xA6: case 0xB6:
|
|
case 0xAE: case 0xBE:
|
|
X = read8(argument_addr);
|
|
result = X;
|
|
setflags();
|
|
break;
|
|
//LDY
|
|
case 0xA0: case 0xA4: case 0xB4:
|
|
case 0xAC: case 0xBC:
|
|
Y = read8(argument_addr);
|
|
result = Y;
|
|
setflags();
|
|
break;
|
|
//LSR A
|
|
case 0x4A:
|
|
value8 = A;
|
|
result = value8 >> 1;
|
|
result |= (value8&0x1)?0x8000:0;
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//LSR
|
|
case 0x46: case 0x56: case 0x4E:
|
|
case 0x5E:
|
|
value8 = read8(argument_addr);
|
|
result = value8 >> 1;
|
|
result |= (value8&0x1)?0x8000:0;
|
|
setflags();
|
|
write8(argument_addr, result&0xFF);
|
|
break;
|
|
//NOP
|
|
case 0xEA:
|
|
break;
|
|
//ORA
|
|
case 0x09: case 0x05: case 0x15:
|
|
case 0x0D: case 0x1D: case 0x19:
|
|
case 0x01: case 0x11:
|
|
value8 = read8(argument_addr);
|
|
result = A | value8;
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//PHA
|
|
case 0x48:
|
|
push8(A);
|
|
break;
|
|
//PHP
|
|
case 0x08:
|
|
push8(SR|SR_BRK);
|
|
break;
|
|
//PLA
|
|
case 0x68:
|
|
result = pull8();
|
|
setflags();
|
|
A = result;
|
|
break;
|
|
//PLP
|
|
case 0x28:
|
|
SR = pull8() | SR_FIXED_BITS;
|
|
break;
|
|
//ROL A
|
|
case 0x2A:
|
|
value16 = (unsigned short)A;
|
|
result = (value16 << 1) | (SR&SR_CARRY);
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//ROL
|
|
case 0x26: case 0x36: case 0x2E:
|
|
case 0x3E:
|
|
value16 = (unsigned short)read8(argument_addr);
|
|
result = (value16 << 1) | (SR&SR_CARRY);
|
|
setflags();
|
|
write8(argument_addr, result&0xFF);
|
|
break;
|
|
//ROR A
|
|
case 0x6A:
|
|
value16 = (unsigned short)A;
|
|
result = (value16 >> 1) | ((SR&SR_CARRY) << 7);
|
|
result |= (value16&0x1)?0x8000:0;
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//ROR
|
|
case 0x66: case 0x76: case 0x6E:
|
|
case 0x7E:
|
|
value16 = (unsigned short)read8(argument_addr);
|
|
result = (value16 >> 1) | ((SR&SR_CARRY) << 7);
|
|
result |= (value16&0x1)?0x8000:0;
|
|
setflags();
|
|
write8(argument_addr, result&0xFF);
|
|
break;
|
|
//RTI
|
|
case 0x40:
|
|
SR = pull8();
|
|
PC = pull16();
|
|
break;
|
|
//RTS
|
|
case 0x60:
|
|
PC = pull16() + 1;
|
|
break;
|
|
//SBC
|
|
case 0xE9: case 0xE5: case 0xF5:
|
|
case 0xED: case 0xFD: case 0xF9:
|
|
case 0xE1: case 0xF1:
|
|
value16 = ((unsigned short)read8(argument_addr)) ^ 0x00FF;
|
|
result = (unsigned short)A + value16 + (unsigned short)(SR&SR_CARRY);
|
|
setflags();
|
|
A = result&0xFF;
|
|
break;
|
|
//SEC
|
|
case 0x38:
|
|
SR |= SR_CARRY;
|
|
break;
|
|
//SED
|
|
case 0xF8:
|
|
SR |= SR_DEC;
|
|
break;
|
|
//SEI
|
|
case 0x78:
|
|
SR |= SR_INT;
|
|
break;
|
|
//STA
|
|
case 0x85: case 0x95: case 0x8D:
|
|
case 0x9D: case 0x99: case 0x81:
|
|
case 0x91:
|
|
write8(argument_addr, A);
|
|
break;
|
|
//STX
|
|
case 0x86: case 0x96: case 0x8E:
|
|
write8(argument_addr, X);
|
|
break;
|
|
//STY
|
|
case 0x84: case 0x94: case 0x8C:
|
|
write8(argument_addr, Y);
|
|
break;
|
|
//TAX
|
|
case 0xAA:
|
|
X = A;
|
|
result = A;
|
|
setflags();
|
|
break;
|
|
//TAY
|
|
case 0xA8:
|
|
Y = A;
|
|
result = A;
|
|
setflags();
|
|
break;
|
|
//TSX
|
|
case 0xBA:
|
|
X = STP;
|
|
result = STP;
|
|
setflags();
|
|
break;
|
|
//TXA
|
|
case 0x8A:
|
|
A = X;
|
|
result = X;
|
|
setflags();
|
|
break;
|
|
//TXS
|
|
case 0x9A:
|
|
STP = X;
|
|
result = X;
|
|
setflags();
|
|
break;
|
|
//TYA
|
|
case 0x98:
|
|
A = Y;
|
|
result = Y;
|
|
setflags();
|
|
break;
|
|
}
|
|
}
|
|
}
|