ALMOST THERE, carry and overflow logic is probably cooked still and also the illegal opcodes are missing but !!!
This commit is contained in:
parent
af294074aa
commit
e7760e8580
5 changed files with 471 additions and 351 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,3 +7,5 @@
|
|||
.vscode/
|
||||
.vs/
|
||||
.idea/
|
||||
*.bin
|
||||
*.bak
|
||||
|
|
4
aiko.php → aiko
Normal file → Executable file
4
aiko.php → aiko
Normal file → Executable 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);
|
||||
}
|
796
src/Cpu.php
796
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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' : '-',
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue