From e7760e85802ead3159f3998345b5e2ef1f9dc56b Mon Sep 17 00:00:00 2001 From: flashwave Date: Sat, 14 Dec 2024 04:47:34 +0000 Subject: [PATCH] ALMOST THERE, carry and overflow logic is probably cooked still and also the illegal opcodes are missing but !!! --- .gitignore | 2 + aiko.php => aiko | 4 +- src/Cpu.php | 796 +++++++++++++++++++++++++++------------------- src/CpuState.php | 16 +- src/FileCpuIo.php | 4 +- 5 files changed, 471 insertions(+), 351 deletions(-) rename aiko.php => aiko (91%) mode change 100644 => 100755 diff --git a/.gitignore b/.gitignore index 17384cd..6a7f5c5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ .vscode/ .vs/ .idea/ +*.bin +*.bak diff --git a/aiko.php b/aiko old mode 100644 new mode 100755 similarity index 91% rename from aiko.php rename to aiko index 72308cd..2da3f22 --- a/aiko.php +++ b/aiko @@ -1,3 +1,4 @@ +#!/usr/bin/env php state->pc = 0x400; for(;;) { printf('[ %s ]%s', $cpu->state, PHP_EOL); $cpu->tick(); - usleep(100_000); + //usleep(10_000); } diff --git a/src/Cpu.php b/src/Cpu.php index 03e9759..ff244f2 100644 --- a/src/Cpu.php +++ b/src/Cpu.php @@ -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); - - // ??? 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); - - // ??? 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); - - // ??? 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); - - // ??? 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); - - // ??? 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); - - // ??? 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); - - // ??? (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); - - // ??? (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); - - else - printf('??? $%02X%s', $num, PHP_EOL); - } finally { - return $cycles; + default: + return 0; } } + + 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; + + case 0x58: // CLI/SEI + $this->state->pIrqDisable = ($opcode & 0x20) > 0; + printf('CLI/SEI%s', PHP_EOL); + return 2; + + case 0xD8: // CLD/SED + $this->state->pDecimalMode = ($opcode & 0x20) > 0; + printf('CLD/SED%s', PHP_EOL); + return 2; + + default: + return 0; + } + } + + 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; + + $source = match($opcode & 0xC0) { + 0x00 => $this->state->pNegative, + 0x40 => $this->state->pOverflow, + 0x80 => $this->state->pCarry, + 0xC0 => $this->state->pZero, + _ => false, // how + }; + + if($source === ($opcode & 0x20) > 0) { + ++$cycles; + printf('PC Displacement: %d%s', $offset, 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); + } + } + + return $cycles; + } } diff --git a/src/CpuState.php b/src/CpuState.php index 6949560..063edf6 100644 --- a/src/CpuState.php +++ b/src/CpuState.php @@ -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' : '-', diff --git a/src/FileCpuIo.php b/src/FileCpuIo.php index 5aa97f5..b7c445a 100644 --- a/src/FileCpuIo.php +++ b/src/FileCpuIo.php @@ -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));