ALMOST THERE, carry and overflow logic is probably cooked still and also the illegal opcodes are missing but !!!

This commit is contained in:
Pachira 2024-12-14 04:47:34 +00:00
parent af294074aa
commit e7760e8580
5 changed files with 471 additions and 351 deletions

2
.gitignore vendored
View file

@ -7,3 +7,5 @@
.vscode/
.vs/
.idea/
*.bin
*.bak

4
aiko.php → aiko Normal file → Executable file
View file

@ -1,3 +1,4 @@
#!/usr/bin/env php
<?php
namespace Aiko;
@ -13,6 +14,7 @@ mb_internal_encoding('UTF-8');
date_default_timezone_set('GMT');
define('AIKO_ROM', '/home/flash/6502_65C02_functional_tests/bin_files/6502_functional_test.bin');
//define('AIKO_ROM', AIKO_ROOT . '/test.bin');
$cpu = new Cpu(
FileCpuIo::open(AIKO_ROM)
@ -32,5 +34,5 @@ $cpu->state->pc = 0x400;
for(;;) {
printf('[ %s ]%s', $cpu->state, PHP_EOL);
$cpu->tick();
usleep(100_000);
//usleep(10_000);
}

View file

@ -23,6 +23,15 @@ class Cpu {
$this->state = $state ?? new CpuState;
}
private function readNextIoU8(): int {
return $this->io->read($this->state->pc++);
}
private function readNextIoU16(): int {
return $this->io->read($this->state->pc++)
| ($this->io->read($this->state->pc++) << 8);
}
private function readIoU16(int $addr): int {
return $this->io->read($addr) | ($this->io->read($addr + 1) << 8);
}
@ -81,360 +90,477 @@ class Cpu {
$this->state->pc = $this->readIoU16($vector);
}
public function tick(): int {
$cycles = 0;
try {
$this->handleInterrupt();
$num = $this->io->read($this->state->pc++);
printf('0b%08b%s', $num, PHP_EOL);
// ??? sig
if($num === 0x00) // 0000_0000
printf('BRK sig%s', PHP_EOL);
// Flow control
elseif($num === 0x40) // 0100_0000
private function handleExactOpcodes(int $opcode): int {
switch($opcode) {
case 0x00: // BRK
$sig = $this->readNextIoU8();
$this->pushU16($this->state->pc);
$this->pushU8($this->state->p | CpuState::P_BREAK);
$this->state->pIrqDisable = true;
$this->state->pc = $this->readIoU16(self::IV_IRQ);
printf('BRK%s', PHP_EOL);
return 7;
case 0x20: // JSR
$addr = $this->readNextIoU8();
$this->pushU16($this->state->pc);
$addr |= $this->readNextIoU8() << 8;
$this->state->pc = $addr;
printf('JSR%s', PHP_EOL);
return 6;
case 0x40: // RTI
$this->state->p = $this->popU8();
$this->state->pc = $this->popU16();
printf('RTI%s', PHP_EOL);
elseif($num === 0x60) // 0110_0000
return 6;
case 0x60: // RTS
$this->state->pc = $this->popU16() + 1;
printf('RTS%s', PHP_EOL);
return 6;
// NOP
elseif($num === 0xEA) // 1110_1010
printf('NOP%s', PHP_EOL);
// ??? (abs)
elseif($num === 0x6C) // 0110_1100
printf('JMP (abs)%s', PHP_EOL); // bugged behaviour
// Implicit Y
elseif($num === 0x88) // 1000_1000
printf('DEY%s', PHP_EOL);
elseif($num === 0xC8) // 1100_1000
printf('INY%s', PHP_EOL);
// Implicit X
elseif($num === 0xCA) // 1100_1010
printf('DEX%s', PHP_EOL);
elseif($num === 0xE8) // 1110_1000
printf('INX%s', PHP_EOL);
// Status stacking
elseif($num === 0x08) // 0000_1000
case 0x08: // PHP
$this->pushU8($this->state->p);
printf('PHP%s', PHP_EOL);
elseif($num === 0x28) // 0010_1000
return 3;
case 0x28: // PLP
$this->state->p = $this->popU8();
printf('PLP%s', PHP_EOL);
elseif($num === 0x48) // 0100_1000
return 4;
case 0x48: // PHA
$this->pushU8($this->state->a);
printf('PHA%s', PHP_EOL);
elseif($num === 0x68) // 0110_1000
return 3;
case 0x68: // PLA
$this->state->a = $this->popU8();
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('PLA%s', PHP_EOL);
return 4;
// Register transfers
elseif($num === 0x8A) // 1000_1010
case 0x88: // DEY
--$this->state->y;
$this->state->pNegative = ($this->state->y & 0x80) > 0;
$this->state->pZero = $this->state->y === 0;
printf('DEY%s', PHP_EOL);
return 2;
case 0xC8: // INY
++$this->state->y;
$this->state->pNegative = ($this->state->y & 0x80) > 0;
$this->state->pZero = $this->state->y === 0;
printf('INY%s', PHP_EOL);
return 2;
case 0x8A: // TXA
$this->state->a = $this->state->x;
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('TXA%s', PHP_EOL);
elseif($num === 0x98) // 1001_1000
return 2;
case 0x98: // TYA
$this->state->a = $this->state->y;
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('TYA%s', PHP_EOL);
elseif($num === 0x9A) // 1001_1010
return 2;
case 0x9A: // TXS
$this->state->s = $this->state->x;
printf('TXS%s', PHP_EOL);
elseif($num === 0xA8) // 1010_1000
return 2;
case 0xA8: // TAY
$this->state->y = $this->state->a;
$this->state->pNegative = ($this->state->y & 0x80) > 0;
$this->state->pZero = $this->state->y === 0;
printf('TAY%s', PHP_EOL);
elseif($num === 0xAA) // 1010_1010
return 2;
case 0xAA: // TAX
$this->state->x = $this->state->a;
$this->state->pNegative = ($this->state->x & 0x80) > 0;
$this->state->pZero = $this->state->x === 0;
printf('TAX%s', PHP_EOL);
elseif($num === 0xBA) // 1011_1010
return 2;
case 0xBA: // TSX
$this->state->x = $this->state->s;
$this->state->pNegative = ($this->state->x & 0x80) > 0;
$this->state->pZero = $this->state->x === 0;
printf('TSX%s', PHP_EOL);
return 2;
// Flag management
elseif($num === 0x18) // 0001_1000
printf('CLC%s', PHP_EOL);
elseif($num === 0x38) // 0011_1000
printf('SEC%s', PHP_EOL);
elseif($num === 0x58) // 0101_1000
printf('CLI%s', PHP_EOL);
elseif($num === 0x78) // 0111_1000
printf('SEI%s', PHP_EOL);
elseif($num === 0xB8) // 1011_1000
case 0xB8: // CLV
$this->state->pOverflow = false;
printf('CLV%s', PHP_EOL);
elseif($num === 0xD8) // 1101_1000
printf('CLD%s', PHP_EOL);
elseif($num === 0xF8) // 1111_1000
printf('SED%s', PHP_EOL);
return 2;
// ??? A
elseif($num === 0x0A) // 0000_1010
printf('ASL A%s', PHP_EOL);
elseif($num === 0x4A) // 0100_1010
printf('LSR A%s', PHP_EOL);
elseif($num === 0x2A) // 0010_1010
printf('ROL A%s', PHP_EOL);
elseif($num === 0x6A) // 0110_1010
printf('ROR A%s', PHP_EOL);
case 0xCA: // DEX
--$this->state->x;
$this->state->pNegative = ($this->state->x & 0x80) > 0;
$this->state->pZero = $this->state->x === 0;
printf('DEX%s', PHP_EOL);
return 2;
case 0xE8: // INX
++$this->state->x;
$this->state->pNegative = ($this->state->x & 0x80) > 0;
$this->state->pZero = $this->state->x === 0;
printf('INX%s', PHP_EOL);
return 2;
// ??? #num
elseif($num === 0x69) // 0110_1001
printf('ADC #num%s', PHP_EOL);
elseif($num === 0x29) // 0010_1001
printf('AND #num%s', PHP_EOL);
elseif($num === 0xC9) // 1100_1001
printf('CMP #num%s', PHP_EOL);
elseif($num === 0xE0) // 1110_0000
printf('CPX #num%s', PHP_EOL);
elseif($num === 0xC0) // 1100_0000
printf('CPY #num%s', PHP_EOL);
elseif($num === 0x49) // 0100_1001
printf('EOR #num%s', PHP_EOL);
elseif($num === 0xA9) // 1010_1001
printf('LDA #num%s', PHP_EOL);
elseif($num === 0xA2) // 1010_0010
printf('LDX #num%s', PHP_EOL);
elseif($num === 0xA0) // 1010_0000
printf('LDY #num%s', PHP_EOL);
elseif($num === 0x09) // 0000_1001
printf('ORA #num%s', PHP_EOL);
elseif($num === 0xE9) // 1110_1001
printf('SBC #num%s', PHP_EOL);
case 0xEA: // NOP
printf('NOP%s', PHP_EOL);
return 2;
// ??? abs
elseif($num === 0x6D) // 0110_1101
printf('ADC abs%s', PHP_EOL);
elseif($num === 0x2D) // 0010_1101
printf('AND abs%s', PHP_EOL);
elseif($num === 0x0E) // 0000_1110
printf('ASL abs%s', PHP_EOL);
elseif($num === 0x2C) // 0010_1100
printf('BIT abs%s', PHP_EOL);
elseif($num === 0xCD) // 1100_1101
printf('CMP abs%s', PHP_EOL);
elseif($num === 0xEC) // 1110_1100
printf('CPX abs%s', PHP_EOL);
elseif($num === 0xCC) // 1100_1100
printf('CPY abs%s', PHP_EOL);
elseif($num === 0xCE) // 1100_1110
printf('DEC abs%s', PHP_EOL);
elseif($num === 0x4D) // 0100_1101
printf('EOR abs%s', PHP_EOL);
elseif($num === 0xEE) // 1110_1110
printf('INC abs%s', PHP_EOL);
elseif($num === 0x4C) // 0100_1100
printf('JMP abs%s', PHP_EOL);
elseif($num === 0x20) // 0010_0000
printf('JSR abs%s', PHP_EOL);
elseif($num === 0xAD) // 1010_1101
printf('LDA abs%s', PHP_EOL);
elseif($num === 0xAE) // 1010_1110
printf('LDX abs%s', PHP_EOL);
elseif($num === 0xAC) // 1010_1100
printf('LDY abs%s', PHP_EOL);
elseif($num === 0x4E) // 0100_1110
printf('LSR abs%s', PHP_EOL);
elseif($num === 0x0D) // 0000_1101
printf('ORA abs%s', PHP_EOL);
elseif($num === 0x2E) // 0010_1110
printf('ROL abs%s', PHP_EOL);
elseif($num === 0x6E) // 0110_1110
printf('ROR abs%s', PHP_EOL);
elseif($num === 0xED) // 1110_1101
printf('SBC abs%s', PHP_EOL);
elseif($num === 0x8D) // 1000_1101
printf('STA abs%s', PHP_EOL);
elseif($num === 0x8E) // 1000_1110
printf('STX abs%s', PHP_EOL);
elseif($num === 0x8C) // 1000_1100
printf('STY abs%s', PHP_EOL);
default:
return 0;
}
}
// ??? rel
elseif($num === 0x90) // 1001_0000
printf('BCC rel%s', PHP_EOL);
elseif($num === 0xB0) // 1011_0000
printf('BCS rel%s', PHP_EOL);
elseif($num === 0xF0) // 1111_0000
printf('BEQ rel%s', PHP_EOL);
elseif($num === 0x30) // 0011_0000
printf('BMI rel%s', PHP_EOL);
elseif($num === 0xD0) // 1101_0000
printf('BNE rel%s', PHP_EOL);
elseif($num === 0x10) // 0001_0000
printf('BPL rel%s', PHP_EOL);
elseif($num === 0x50) // 0101_0000
printf('BVC rel%s', PHP_EOL);
elseif($num === 0x70) // 0111_0000
printf('BVS rel%s', PHP_EOL);
private function handleFlagOpcodes(int $opcode): int {
switch($opcode & 0xDF) {
case 0x18: // CLC/SEC
$this->state->pCarry = ($opcode & 0x20) > 0;
printf('CLC/SEC%s', PHP_EOL);
return 2;
// ??? zp
elseif($num === 0x65) // 0110_0101
printf('ADC zp%s', PHP_EOL);
elseif($num === 0x25) // 0010_0101
printf('AND zp%s', PHP_EOL);
elseif($num === 0x06) // 0000_0110
printf('ASL zp%s', PHP_EOL);
elseif($num === 0x24) // 0010_0100
printf('BIT zp%s', PHP_EOL);
elseif($num === 0xC5) // 1100_0101
printf('CMP zp%s', PHP_EOL);
elseif($num === 0xE4) // 1110_0100
printf('CPX zp%s', PHP_EOL);
elseif($num === 0xC4) // 1100_0100
printf('CPY zp%s', PHP_EOL);
elseif($num === 0xC6) // 1100_0110
printf('DEC zp%s', PHP_EOL);
elseif($num === 0x45) // 0100_0101
printf('EOR zp%s', PHP_EOL);
elseif($num === 0xE6) // 1110_0110
printf('INC zp%s', PHP_EOL);
elseif($num === 0xA5) // 1010_0101
printf('LDA zp%s', PHP_EOL);
elseif($num === 0xA6) // 1010_0110
printf('LDX zp%s', PHP_EOL);
elseif($num === 0xA4) // 1010_0100
printf('LDY zp%s', PHP_EOL);
elseif($num === 0x46) // 0010_0110
printf('LSR zp%s', PHP_EOL);
elseif($num === 0x05) // 0000_0101
printf('ORA zp%s', PHP_EOL);
elseif($num === 0x26) // 0010_0110
printf('ROL zp%s', PHP_EOL);
elseif($num === 0x66) // 0110_0110
printf('ROR zp%s', PHP_EOL);
elseif($num === 0xE5) // 1110_0101
printf('SBC zp%s', PHP_EOL);
elseif($num === 0x85) // 1000_0101
printf('STA zp%s', PHP_EOL);
elseif($num === 0x86) // 1000_0110
printf('STX zp%s', PHP_EOL);
elseif($num === 0x84) // 1000_0100
printf('STY zp%s', PHP_EOL);
case 0x58: // CLI/SEI
$this->state->pIrqDisable = ($opcode & 0x20) > 0;
printf('CLI/SEI%s', PHP_EOL);
return 2;
// ??? abs,X
elseif($num === 0x7D) // 0111_1101
printf('ADC abs,X%s', PHP_EOL);
elseif($num === 0x3D) // 0011_1101
printf('AND abs,X%s', PHP_EOL);
elseif($num === 0x1E) // 0001_1110
printf('ASL abs,X%s', PHP_EOL);
elseif($num === 0xDD) // 1101_1101
printf('CMP abs,X%s', PHP_EOL);
elseif($num === 0xDE) // 1101_1110
printf('DEC abs,X%s', PHP_EOL);
elseif($num === 0x5D) // 0101_1101
printf('EOR abs,X%s', PHP_EOL);
elseif($num === 0xFE) // 1111_1110
printf('INC abs,X%s', PHP_EOL);
elseif($num === 0xBD) // 1011_1101
printf('LDA abs,X%s', PHP_EOL);
elseif($num === 0xBC) // 1011_1100
printf('LDY abs,X%s', PHP_EOL);
elseif($num === 0x5E) // 0101_1110
printf('LSR abs,X%s', PHP_EOL);
elseif($num === 0x1D) // 0001_1101
printf('ORA abs,X%s', PHP_EOL);
elseif($num === 0x3E) // 0011_1110
printf('ROL abs,X%s', PHP_EOL);
elseif($num === 0x7E) // 0111_1110
printf('ROR abs,X%s', PHP_EOL);
elseif($num === 0xFD) // 1111_1101
printf('SBC abs,X%s', PHP_EOL);
elseif($num === 0x9D) // 1001_1101
printf('STA abs,X%s', PHP_EOL);
case 0xD8: // CLD/SED
$this->state->pDecimalMode = ($opcode & 0x20) > 0;
printf('CLD/SED%s', PHP_EOL);
return 2;
// ??? abs,Y
elseif($num === 0x79) // 0111_1001
printf('ADC abs,Y%s', PHP_EOL);
elseif($num === 0x39) // 0011_1001
printf('AND abs,Y%s', PHP_EOL);
elseif($num === 0xD9) // 1101_1001
printf('CMP abs,Y%s', PHP_EOL);
elseif($num === 0x59) // 0101_1001
printf('EOR abs,Y%s', PHP_EOL);
elseif($num === 0xB9) // 1011_1001
printf('LDA abs,Y%s', PHP_EOL);
elseif($num === 0xBE) // 1011_1110
printf('LDX abs,Y%s', PHP_EOL);
elseif($num === 0x19) // 0001_1001
printf('ORA abs,Y%s', PHP_EOL);
elseif($num === 0xF9) // 1111_1001
printf('SBC abs,Y%s', PHP_EOL);
elseif($num === 0x99) // 1001_1001
printf('STA abs,Y%s', PHP_EOL);
default:
return 0;
}
}
// ??? zp,X
elseif($num === 0x75) // 0111_0101
printf('ADC zp,X%s', PHP_EOL);
elseif($num === 0x35) // 0011_0101
printf('AND zp,X%s', PHP_EOL);
elseif($num === 0x16) // 0001_0110
printf('ASL zp,X%s', PHP_EOL);
elseif($num === 0xD5) // 1101_0101
printf('CMP zp,X%s', PHP_EOL);
elseif($num === 0xD6) // 1101_0110
printf('DEC zp,X%s', PHP_EOL);
elseif($num === 0x55) // 0101_0101
printf('EOR zp,X%s', PHP_EOL);
elseif($num === 0xF6) // 1111_0110
printf('INC zp,X%s', PHP_EOL);
elseif($num === 0xB5) // 1011_0101
printf('LDA zp,X%s', PHP_EOL);
elseif($num === 0xB4) // 1011_0100
printf('LDY zp,X%s', PHP_EOL);
elseif($num === 0x56) // 0101_0110
printf('LSR zp,X%s', PHP_EOL);
elseif($num === 0x15) // 0001_0101
printf('ORA zp,X%s', PHP_EOL);
elseif($num === 0x36) // 0011_0110
printf('ROL zp,X%s', PHP_EOL);
elseif($num === 0x76) // 0111_0110
printf('ROR zp,X%s', PHP_EOL);
elseif($num === 0xF5) // 1111_0101
printf('SBC zp,X%s', PHP_EOL);
elseif($num === 0x95) // 1001_0101
printf('STA zp,X%s', PHP_EOL);
elseif($num === 0x94) // 1001_0100
printf('STY zp,X%s', PHP_EOL);
private function handleBranchOpcodes(int $opcode): int {
// BCC/BCS/BEQ/BMI/BNE/BPL/BVC/BVS
if(($opcode & 0x1F) === 0x10) {
$cycles = 2;
$offset = $this->readNextIoU8();
if($offset > 0x7F) // signed byte
$offset -= 0x100;
// ??? zp,Y
elseif($num === 0xB6) // 1101_0110
printf('LDX zp,Y%s', PHP_EOL);
elseif($num === 0x96) // 1001_0110
printf('STX zp,Y%s', PHP_EOL);
$source = match($opcode & 0xC0) {
0x00 => $this->state->pNegative,
0x40 => $this->state->pOverflow,
0x80 => $this->state->pCarry,
0xC0 => $this->state->pZero,
_ => false, // how
};
// ??? (zp,X)
elseif($num === 0x61) // 0110_0001
printf('ADC (zp,X)%s', PHP_EOL);
elseif($num === 0x21) // 0010_0001
printf('AND (zp,X)%s', PHP_EOL);
elseif($num === 0xC1) // 1100_0001
printf('CMP (zp,X)%s', PHP_EOL);
elseif($num === 0x41) // 0100_0001
printf('EOR (zp,X)%s', PHP_EOL);
elseif($num === 0xA1) // 1010_0001
printf('LDA (zp,X)%s', PHP_EOL);
elseif($num === 0x01) // 0000_0001
printf('ORA (zp,X)%s', PHP_EOL);
elseif($num === 0xE1) // 1110_0001
printf('SBC (zp,X)%s', PHP_EOL);
elseif($num === 0x81) // 1000_0001
printf('STA (zp,X)%s', PHP_EOL);
if($source === ($opcode & 0x20) > 0) {
++$cycles;
printf('PC Displacement: %d%s', $offset, PHP_EOL);
// ??? (zp),Y
elseif($num === 0x71) // 0111_0001
printf('ADC (zp),Y%s', PHP_EOL);
elseif($num === 0x31) // 0011_0001
printf('AND (zp),Y%s', PHP_EOL);
elseif($num === 0xD1) // 1101_0001
printf('CMP (zp),Y%s', PHP_EOL);
elseif($num === 0x51) // 0101_0001
printf('EOR (zp),Y%s', PHP_EOL);
elseif($num === 0xB1) // 1011_0001
printf('LDA (zp),Y%s', PHP_EOL);
elseif($num === 0x11) // 0001_0001
printf('ORA (zp),Y%s', PHP_EOL);
elseif($num === 0xF1) // 1111_0001
printf('SBC (zp),Y%s', PHP_EOL);
elseif($num === 0x91) // 1001_0001
printf('STA (zp),Y%s', PHP_EOL);
$target = $this->state->pc + $offset;
if(($this->state->pc & 0xFF00) !== ($target & 0xFF00))
++$cycles;
// dont forget cycle counting
$this->state->pc = $target;
}
printf('BCC/BCS/BEQ/BMI/BNE/BPL/BVC/BVS%s', PHP_EOL);
return $cycles;
}
return 0;
}
private function handleCommonOpcodes(int $opcode): int {
$cycles = 2;
$addr = null;
$value = null;
// Addressing modes
switch($opcode & 0x1F) {
// (zp,X)
case 0x01: // ALU
$cycles += 4;
$addr = $this->io->read($this->readNextIoU8() + $this->state->x);
printf('(zp,X)%s', PHP_EOL);
break;
// zp
case 0x04: // CTRL
case 0x05: // ALU
case 0x06: // RMW
$cycles += 1;
$addr = $this->readNextIoU8();
printf('zp%s', PHP_EOL);
break;
// #const
case 0x00: // CTRL
case 0x02: // RMW
case 0x09: // ALU
$value = $this->readNextIoU8();
printf('#const%s', PHP_EOL);
break;
// A
case 0x0A: // RMW
$value = $this->state->a;
printf('A%s', PHP_EOL);
break;
// abs
case 0x0C: // CTRL
case 0x0D: // ALU
case 0x0E: // RMW
$cycles += 2;
$addr = $this->readNextIoU16();
printf('abs%s', PHP_EOL);
break;
// (zp),Y
case 0x11: // ALU
$cycles += 3;
$tmp = $this->io->read($this->readNextIoU8());
$addr = $tmp + $this->state->y;
if(($tmp & 0xFF00) != ($addr & 0xFF00))
$cycles += 1;
printf('(zp),Y%s', PHP_EOL);
break;
// zp,X
case 0x14: // CTRL
case 0x15: // ALU
case 0x16: // RMW
$cycles += 2;
$addr = $this->readNextIoU8() + $this->state->x;
printf('zp,X%s', PHP_EOL);
break;
// zp,Y
case 0x1A: // RMW
$cycles += 2;
$addr = $this->readNextIoU8() + $this->state->y;
printf('zp,Y%s', PHP_EOL);
break;
// abs,Y
case 0x19: // ALU
case 0x1E: // RMW
$cycles += 2;
$tmp = $this->readNextIoU16();
$addr = $tmp + $this->state->y;
if(($tmp & 0xFF00) != ($addr & 0xFF00))
$cycles += 1;
printf('abs,Y%s', PHP_EOL);
break;
// abs,X
case 0x1C: // CTRL
case 0x1D: // ALU
case 0x1E: // RMW
$cycles += 2;
$tmp = $this->readNextIoU16();
$addr = $tmp + $this->state->x;
if(($tmp & 0xFF00) != ($addr & 0xFF00))
$cycles += 1;
printf('abs,X%s', PHP_EOL);
break;
default:
$addr = $value = 0;
printf('Invalid addressing mode!!!%s', PHP_EOL);
return 0;
}
$getValue = fn() => $value ?? $this->io->read($addr);
// Instructions
switch($opcode & 0xE3) {
// CTRL
case 0x20: // BIT
$value = $getValue();
$this->state->p &= 0x3D;
$this->state->p |= $value & 0xC0;
$this->state->pZero = ($this->state->a & $value) === 0;
printf('BIT%s', PHP_EOL);
break;
case 0x40: // JMP
$this->state->pc = $addr;
printf('JMP%s', PHP_EOL);
break;
case 0x60: // JMP indirect
$addrLo = $addr;
$addrHi = $addrLo + 1;
if(($addrLo & 0xFF) === 0xFF)
$addrHi -= 0x100;
$addr = $this->io->read($addrLo) | ($this->io->read($addrHi) << 8);
printf('JMP (abs)%s', PHP_EOL);
break;
case 0x80: // STY
$this->io->write($addr, $this->state->y);
printf('STY%s', PHP_EOL);
break;
case 0xA0: // LDY
$this->state->y = $getValue();
$this->state->pNegative = ($this->state->y & 0x80) > 0;
$this->state->pZero = $this->state->y === 0;
printf('LDY%s', PHP_EOL);
break;
case 0xC0: // CPY
$result = ((0x100 | $this->state->y) - $getValue()) & 0xFF;
$this->state->pNegative = ($result & 0x80) > 0;
$this->state->pZero = $result === 0;
$this->state->pCarry = $result >= 0;
printf('CPY%s', PHP_EOL);
break;
case 0xE0: // CPX
$result = ((0x100 | $this->state->x) - $getValue()) & 0xFF;
$this->state->pNegative = ($result & 0x80) > 0;
$this->state->pZero = $result === 0;
$this->state->pCarry = $result >= 0;
printf('CPX%s', PHP_EOL);
break;
// ALU
case 0x01: // ORA
$this->state->a |= $getValue();
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('ORA%s', PHP_EOL);
break;
case 0x21: // AND
$this->state->a &= $getValue();
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('AND%s', PHP_EOL);
break;
case 0x41: // EOR
$this->state->a ^= $getValue();
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('EOR%s', PHP_EOL);
break;
case 0x61: // ADC
$value = $getValue();
$tmp = $this->state->a + $value;
if($this->state->pCarry)
$tmp += 1;
$this->state->pNegative = ($tmp & 0x80) > 0;
$this->state->pOverflow = $value < $tmp; // does this make sense?
$this->state->pZero = $tmp === 0;
$this->state->pCarry = ($tmp & 0x100) > 0;
$this->state->a = $tmp;
printf('ADC%s', PHP_EOL);
break;
case 0x81: // STA
// (zp),Y, abs,Y and abs,X always take 5 cycles!!
$this->io->write($addr, $this->state->a);
printf('STA%s', PHP_EOL);
break;
case 0xA1: // LDA
$this->state->a = $getValue();
$this->state->pNegative = ($this->state->a & 0x80) > 0;
$this->state->pZero = $this->state->a === 0;
printf('LDA%s', PHP_EOL);
break;
case 0xC1: // CMP
$result = ((0x100 | $this->state->a) - $getValue()) & 0xFF;
$this->state->pNegative = ($result & 0x80) > 0;
$this->state->pZero = $result === 0;
$this->state->pCarry = $result >= 0;
printf('CMP%s', PHP_EOL);
break;
case 0xE1: // SBC
$value = $getValue();
$tmp = $this->state->a + $value;
if(!$this->state->pCarry)
$tmp -= 1;
$this->state->pNegative = ($tmp & 0x80) > 0;
$this->state->pOverflow = $value > $tmp; // does this make sense?
$this->state->pZero = $tmp === 0;
$this->state->pCarry = ($tmp & 0x100) > 0;
$this->state->a = $tmp;
printf('SBC%s', PHP_EOL);
break;
// RMW
case 0x02: // ASL
case 0x22: // ROL
$tmp = $getValue();
$this->state->pNegative = ($tmp & 0x40) > 0;
$this->state->pZero = $tmp === 0;
$this->state->pCarry = $carry = ($tmp & 0x80) > 0;
$tmp <<= 1;
if($carry && ($opcode & 0x20) > 0)
$tmp |= 0x01;
$this->state->a = $tmp;
printf('ASL/ROL%s', PHP_EOL);
break;
case 0x42: // LSR
case 0x62: // ROR
$tmp = $getValue();
$this->state->pNegative = false;
$this->state->pZero = $tmp === 0;
$this->state->pCarry = $carry = ($tmp & 0x01) > 0;
$tmp >>= 1;
if($carry && ($opc & 0x20) > 0)
$tmp |= 0x80;
$this->state->a = $tmp;
printf('LSR/ROR%s', PHP_EOL);
break;
case 0x82: // STX
$this->io->write($addr, $this->state->x);
printf('STX%s', PHP_EOL);
break;
case 0xA2: // LDX
$this->state->x = $getValue();
$this->state->pNegative = ($this->state->x & 0x80) > 0;
$this->state->pZero = $this->state->x === 0;
printf('LDX%s', PHP_EOL);
break;
case 0xC2: // DEC
$result = $getValue() - 1;
$this->state->pNegative = ($result & 0x80) > 0;
$this->state->pZero = $result === 0;
$this->io->write($addr, $result);
printf('DEC%s', PHP_EOL);
break;
case 0xE2: // INC
$result = $getValue() + 1;
$this->state->pNegative = ($result & 0x80) > 0;
$this->state->pZero = $result === 0;
$this->io->write($addr, $result);
printf('INC%s', PHP_EOL);
break;
default:
return 0;
}
return $cycles;
}
public function tick(): int {
//$this->handleInterrupt();
$opcode = $this->readNextIoU8();
printf('0b%08b%s', $opcode, PHP_EOL);
// a candidate for one of the ugliest things i've written, clean this up...
$cycles = $this->handleExactOpcodes($opcode);
if($cycles < 1) {
$cycles = $this->handleFlagOpcodes($opcode);
if($cycles < 1) {
$cycles = $this->handleBranchOpcodes($opcode);
if($cycles < 1)
$cycles = $this->handleCommonOpcodes($opcode);
}
}
else
printf('??? $%02X%s', $num, PHP_EOL);
} finally {
return $cycles;
}
}
}

View file

@ -37,7 +37,8 @@ class CpuState implements Stringable {
}
public int $p = 0 {
set { $this->p = $value & 0xFF; }
// $20 is unused but is set, $10/b never appears outside of the stack
set { $this->p = ($value & 0xCF) | 0x20; }
}
public bool $pCarry {
@ -80,16 +81,6 @@ class CpuState implements Stringable {
}
}
public bool $pBreak {
get { return ($this->p & self::P_BREAK) > 0; }
set {
if($value)
$this->p |= self::P_BREAK;
else
$this->p &= ~self::P_BREAK;
}
}
public bool $pOverflow {
get { return ($this->p & self::P_OVERFLOW) > 0; }
set {
@ -112,11 +103,10 @@ class CpuState implements Stringable {
public function __toString(): string {
return sprintf(
'PC$%04X / A$%02X / X$%02X / Y$%02X / S$%04X / P%s%s-%s%s%s%s%s',
'PC$%04X / A$%02X / X$%02X / Y$%02X / S$%04X / P%s%s--%s%s%s%s',
$this->pc, $this->a, $this->x, $this->y, $this->sPage | $this->s,
$this->pNegative ? 'n' : '-',
$this->pOverflow ? 'v' : '-',
$this->pBreak ? 'b' : '-',
$this->pDecimalMode ? 'd' : '-',
$this->pIrqDisable ? 'i' : '-',
$this->pZero ? 'z' : '-',

View file

@ -41,7 +41,7 @@ class FileCpuIo implements CpuIo {
/** @throws UnexpectedValueException if $readFunc is outside of the expected range. */
public function read(int $addr): int {
printf(' <- $%04X%s', $addr, PHP_EOL);
//printf(' <- $%04X%s', $addr, PHP_EOL);
fseek($this->file, $addr);
@ -53,7 +53,7 @@ class FileCpuIo implements CpuIo {
}
public function write(int $addr, int $num): void {
printf(' -> $%04X = $%02X %s', $addr, $num, PHP_EOL);
//printf(' -> $%04X = $%02X %s', $addr, $num, PHP_EOL);
fseek($this->file, $addr);
fwrite($this->file, chr($num));