initial vommit
This commit is contained in:
commit
471377fe70
15 changed files with 809 additions and 0 deletions
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
@ -0,0 +1,8 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* text=auto
|
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/vendor
|
||||
/composer.local.json
|
||||
/.debug
|
||||
[Tt]humbs.db
|
||||
[Dd]esktop.ini
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.vs/
|
||||
.idea/
|
23
LICENCE
Normal file
23
LICENCE
Normal file
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2024, flashwave <me@flash.moe>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
README.md
Normal file
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# AIKO PHP
|
||||
|
||||
this is a 6502 emulator implemented in PHP 8.4 because uhhh
|
||||
|
||||

|
BIN
aiko-magipoka.gif
Normal file
BIN
aiko-magipoka.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 148 KiB |
36
aiko.php
Normal file
36
aiko.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
define('AIKO_STARTUP', microtime(true));
|
||||
define('AIKO_ROOT', __DIR__);
|
||||
define('AIKO_DEBUG', is_file(AIKO_ROOT . '/.debug'));
|
||||
define('AIKO_SOURCE', AIKO_ROOT . '/src');
|
||||
|
||||
require_once AIKO_ROOT . '/vendor/autoload.php';
|
||||
|
||||
error_reporting(AIKO_DEBUG ? -1 : 0);
|
||||
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');
|
||||
|
||||
$cpu = new Cpu(
|
||||
FileCpuIo::open(AIKO_ROM)
|
||||
// new ClosureCpuIo(
|
||||
// function(int $addr): int {
|
||||
// printf('GET $%04X%s', $addr, PHP_EOL);
|
||||
|
||||
// return $addr & 0xFF;
|
||||
// },
|
||||
// function(int $addr, int $value): void {
|
||||
// printf('SET $%04X -> $%02X %s', $addr, $value, PHP_EOL);
|
||||
// }
|
||||
// )
|
||||
);
|
||||
|
||||
$cpu->state->pc = 0x400;
|
||||
for(;;) {
|
||||
printf('[ %s ]%s', $cpu->state, PHP_EOL);
|
||||
$cpu->tick();
|
||||
usleep(100_000);
|
||||
}
|
10
composer.json
Normal file
10
composer.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"require": {
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Aiko\\": "src"
|
||||
}
|
||||
}
|
||||
}
|
20
composer.lock
generated
Normal file
20
composer.lock
generated
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4cc18c409b485876ac7639b329eab874",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
37
src/ClosureCpuIo.php
Normal file
37
src/ClosureCpuIo.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ClosureCpuIo implements CpuIo {
|
||||
/**
|
||||
* @param callable(int<0, 65535>): int<0, 255> $readFunc
|
||||
* @param callable(int<0, 65535>, int<0, 255>): void $writeFunc
|
||||
*/
|
||||
public function __construct(
|
||||
private $readFunc,
|
||||
private $writeFunc
|
||||
) {}
|
||||
|
||||
/** @throws UnexpectedValueException if $readFunc is outside of the expected range. */
|
||||
public function read(int $addr): int {
|
||||
if($addr < 0 || $addr > 0xFFFF)
|
||||
throw new InvalidArgumentException('$addr is not an unsigned 16-bit integer');
|
||||
|
||||
$value = ($this->readFunc)($addr);
|
||||
if($value < 0 || $value > 0xFF)
|
||||
throw new UnexpectedValueException('return value of $this->readFunc is not an unsigned 8-bit integer');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function write(int $addr, int $num): void {
|
||||
if($addr < 0 || $addr > 0xFFFF)
|
||||
throw new InvalidArgumentException('$addr is not an unsigned 16-bit integer');
|
||||
if($num < 0 || $num > 0xFF)
|
||||
throw new InvalidArgumentException('$addr is not an unsigned 8-bit integer');
|
||||
|
||||
($this->writeFunc)($addr, $num);
|
||||
}
|
||||
}
|
443
src/Cpu.php
Normal file
443
src/Cpu.php
Normal file
|
@ -0,0 +1,443 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Cpu {
|
||||
public const int IV_IRQ = 0xFFFE;
|
||||
public const int IV_RESET = 0xFFFC;
|
||||
public const int IV_NMI = 0xFFFA;
|
||||
|
||||
private CpuInterruptRequest $irq = CpuInterruptRequest::None;
|
||||
|
||||
public private(set) CpuState $state;
|
||||
|
||||
public bool $interruptRequested {
|
||||
get => $this->irq !== CpuInterruptRequest::None;
|
||||
}
|
||||
|
||||
public function __construct(
|
||||
public private(set) CpuIo $io,
|
||||
?CpuState $state = null
|
||||
) {
|
||||
$this->state = $state ?? new CpuState;
|
||||
}
|
||||
|
||||
private function readIoU16(int $addr): int {
|
||||
return $this->io->read($addr) | ($this->io->read($addr + 1) << 8);
|
||||
}
|
||||
|
||||
public function popU8(): int {
|
||||
return $this->io->read($this->state->sPage | $this->state->s++);
|
||||
}
|
||||
|
||||
public function pushU8(int $value): void {
|
||||
$this->io->write($this->state->sPage | $this->state->s--, $value);
|
||||
}
|
||||
|
||||
public function popU16(): int {
|
||||
return $this->popU8() | ($this->popU8() << 8);
|
||||
}
|
||||
|
||||
public function pushU16(int $value): void {
|
||||
$this->pushU8($value & 0xFF);
|
||||
$this->pushU8(($value & 0xFF00) >> 8);
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
$this->irq = CpuInterruptRequest::Reset;
|
||||
}
|
||||
|
||||
public function interrupt(bool $maskable = true): void {
|
||||
$this->irq = $maskable ? CpuInterruptRequest::Maskable : CpuInterruptRequest::NonMaskable;
|
||||
}
|
||||
|
||||
private function handleInterrupt(): void {
|
||||
$irq = $this->irq;
|
||||
if($irq === CpuInterruptRequest::None)
|
||||
return;
|
||||
|
||||
$this->irq = CpuInterruptRequest::None;
|
||||
|
||||
if($irq === CpuInterruptRequest::Reset) {
|
||||
$this->state->pc = $this->readIoU16(self::IV_RESET);
|
||||
return;
|
||||
}
|
||||
|
||||
$pc = $this->state->pc;
|
||||
|
||||
if($irq === CpuInterruptRequest::Break) {
|
||||
$vector = self::IV_IRQ;
|
||||
++$pc;
|
||||
} else
|
||||
$vector = match($irq) {
|
||||
CpuInterruptRequest::Maskable => self::IV_IRQ,
|
||||
CpuInterruptRequest::NonMaskable => self::IV_NMI,
|
||||
default => throw new RuntimeException('unexpected irq value')
|
||||
};
|
||||
|
||||
$this->pushU16($pc);
|
||||
$this->pushU8($this->state->p);
|
||||
|
||||
$this->state->pc = $this->readIoU16($vector);
|
||||
}
|
||||
|
||||
public function tick(): int {
|
||||
$cycles = 0;
|
||||
try {
|
||||
$this->handleInterrupt();
|
||||
|
||||
$opc = $this->io->read($this->state->pc++);
|
||||
printf('0b%08b%s', $opc, PHP_EOL);
|
||||
|
||||
// yeah okay so i still want to implement this whole shtick of decoding opcodes
|
||||
// BUT i'm first going to just wipe all of this below and implement the Gigantic Switch Statement
|
||||
// in order to first properly figure out how to map that all out, in the mean time
|
||||
// enjoy this unfinished garbage cos i want to push something to git :>
|
||||
if($opc === 0) { // Breaking
|
||||
printf('BRK sig%s', PHP_EOL);
|
||||
$this->irq = CpuInterruptRequest::Break;
|
||||
} elseif($opc === 0xEA) { // No-op
|
||||
printf('NOP%s', PHP_EOL);
|
||||
} elseif($opc === 0x6C) { // Relative absolute jump, with bug!
|
||||
printf('JMP (abs)%s', PHP_EOL);
|
||||
|
||||
$addrLo = $this->io->read($this->state->pc++) | ($this->io->read($this->state->pc++) << 8);
|
||||
$addrHi = $addrLo + 1;
|
||||
if(($addrLo & 0xFF) === 0xFF)
|
||||
$addrHi -= 0x100;
|
||||
|
||||
$this->state->pc = $this->io->read($addrLo) | ($this->io->read($addrHi) << 8);
|
||||
} elseif(($opc & 0xDF) === 0x40) { // Returning
|
||||
printf('Returning...%s', PHP_EOL);
|
||||
|
||||
$this->state->pc = $this->popU16();
|
||||
if(($opc & 0x20) > 0)
|
||||
$this->state->p = $this->popU8();
|
||||
} elseif(($opc & 0x1F) == 0x10) { // Branching
|
||||
printf('Branching...%s', PHP_EOL);
|
||||
|
||||
$source = match($opc & 0xC0) {
|
||||
0 => $this->state->pNegative,
|
||||
0x40 => $this->state->pOverflow,
|
||||
0x80 => $this->state->pCarry,
|
||||
0xC0 => $this->state->pZero,
|
||||
};
|
||||
|
||||
$rel = $this->io->read($this->state->pc++);
|
||||
if($rel > 0x7F)
|
||||
$rel -= 0x100;
|
||||
|
||||
if($source === (($opc & 0x20) > 0))
|
||||
$this->state->pc += $rel;
|
||||
} elseif(($opc & 0xDF) === 0x08) { // Pushing state
|
||||
printf('Pushing state...%s', PHP_EOL);
|
||||
|
||||
$this->pushU8($opc & 0x40 ? $this->state->a : $this->state->p);
|
||||
} elseif(($opc & 0xDF) === 0x28) { // Popping state
|
||||
printf('Popping state...%s', PHP_EOL);
|
||||
|
||||
$value = $this->popU8();
|
||||
if($opc & 0x40)
|
||||
$this->state->a = $value;
|
||||
else
|
||||
$this->state->p = $value;
|
||||
} elseif($opc === 0xB8 || ($opc & 0x9F) === 0x18 || ($opc & 0xDF) === 0xD8) { // Flag management
|
||||
printf('Clearing or setting status flag...%s', PHP_EOL);
|
||||
|
||||
$target = $opc & 0xC0;
|
||||
$state = ($opc & 0x20) > 0;
|
||||
|
||||
if($target === 0)
|
||||
$this->state->pCarry = $state;
|
||||
elseif($target === 0x40)
|
||||
$this->state->pIrqDisable = $state;
|
||||
elseif($target === 0x80)
|
||||
$this->state->pCarry = false;
|
||||
elseif($target === 0xC0)
|
||||
$this->state->pDecimalMode = $state;
|
||||
} elseif(($opc & 0xDF) === 0x0A) { // Shift or rotate left
|
||||
printf('Shifting or rotating left...%s', PHP_EOL);
|
||||
|
||||
$this->state->pCarry = ($this->state->a & 0x80) > 0;
|
||||
$this->state->a <<= 1;
|
||||
if(($opc & 0x20) > 0 && $this->state->pCarry)
|
||||
$this->state->a |= 0x01;
|
||||
} elseif(($opc & 0xDF) === 0x4A) { // Shift or rotate right
|
||||
printf('Shifting or rotating right...%s', PHP_EOL);
|
||||
|
||||
$this->state->pCarry = ($this->state->a & 0x01) > 0;
|
||||
$this->state->a >>= 1;
|
||||
if(($opc & 0x20) > 0 && $this->state->pCarry)
|
||||
$this->state->a |= 0x80;
|
||||
} else {
|
||||
if($opc === 0x88) // 1000_1000
|
||||
printf('DEY%s', PHP_EOL);
|
||||
elseif($opc === 0x8A) // 1000_1010
|
||||
printf('TXA%s', PHP_EOL);
|
||||
elseif($opc === 0x98) // 1001_1000
|
||||
printf('TYA%s', PHP_EOL);
|
||||
elseif($opc === 0x9A) // 1001_1010
|
||||
printf('TXS%s', PHP_EOL);
|
||||
elseif($opc === 0xA8) // 1010_1000
|
||||
printf('TAY%s', PHP_EOL);
|
||||
elseif($opc === 0xAA) // 1010_1010
|
||||
printf('TAX%s', PHP_EOL);
|
||||
elseif($opc === 0xBA) // 1011_1010
|
||||
printf('TSX%s', PHP_EOL);
|
||||
elseif($opc === 0xC8) // 1100_1000
|
||||
printf('INY%s', PHP_EOL);
|
||||
elseif($opc === 0xCA) // 1100_1010
|
||||
printf('DEX%s', PHP_EOL);
|
||||
elseif($opc === 0xE8) // 1110_1000
|
||||
printf('INX%s', PHP_EOL);
|
||||
|
||||
// ??? #num
|
||||
elseif($opc === 0x09) // 0000_1001
|
||||
printf('ORA #num%s', PHP_EOL);
|
||||
elseif($opc === 0x29) // 0010_1001
|
||||
printf('AND #num%s', PHP_EOL);
|
||||
elseif($opc === 0x49) // 0100_1001
|
||||
printf('EOR #num%s', PHP_EOL);
|
||||
elseif($opc === 0x69) // 0110_1001
|
||||
printf('ADC #num%s', PHP_EOL);
|
||||
elseif($opc === 0xA0) // 1010_0000
|
||||
printf('LDY #num%s', PHP_EOL);
|
||||
elseif($opc === 0xA2) // 1010_0010
|
||||
printf('LDX #num%s', PHP_EOL);
|
||||
elseif($opc === 0xA9) // 1010_1001
|
||||
printf('LDA #num%s', PHP_EOL);
|
||||
elseif($opc === 0xC0) // 1100_0000
|
||||
printf('CPY #num%s', PHP_EOL);
|
||||
elseif($opc === 0xC9) // 1100_1001
|
||||
printf('CMP #num%s', PHP_EOL);
|
||||
elseif($opc === 0xE0) // 1110_0000
|
||||
printf('CPX #num%s', PHP_EOL);
|
||||
elseif($opc === 0xE9) // 1110_1001
|
||||
printf('SBC #num%s', PHP_EOL);
|
||||
|
||||
// ??? abs
|
||||
elseif($opc === 0x0D) // 0000_1101
|
||||
printf('ORA abs%s', PHP_EOL);
|
||||
elseif($opc === 0x0E) // 0000_1110
|
||||
printf('ASL abs%s', PHP_EOL);
|
||||
elseif($opc === 0x20) // 0010_0000
|
||||
printf('JSR abs%s', PHP_EOL);
|
||||
elseif($opc === 0x2C) // 0010_1100
|
||||
printf('BIT abs%s', PHP_EOL);
|
||||
elseif($opc === 0x2D) // 0010_1101
|
||||
printf('AND abs%s', PHP_EOL);
|
||||
elseif($opc === 0x2E) // 0010_1110
|
||||
printf('ROL abs%s', PHP_EOL);
|
||||
elseif($opc === 0x4C) // 0100_1100
|
||||
printf('JMP abs%s', PHP_EOL);
|
||||
elseif($opc === 0x4D) // 0100_1101
|
||||
printf('EOR abs%s', PHP_EOL);
|
||||
elseif($opc === 0x4E) // 0100_1110
|
||||
printf('LSR abs%s', PHP_EOL);
|
||||
elseif($opc === 0x6D) // 0110_1101
|
||||
printf('ADC abs%s', PHP_EOL);
|
||||
elseif($opc === 0x6E) // 0110_1110
|
||||
printf('ROR abs%s', PHP_EOL);
|
||||
elseif($opc === 0x8C) // 1000_1100
|
||||
printf('STY abs%s', PHP_EOL);
|
||||
elseif($opc === 0x8D) // 1000_1101
|
||||
printf('STA abs%s', PHP_EOL);
|
||||
elseif($opc === 0x8E) // 1000_1110
|
||||
printf('STX abs%s', PHP_EOL);
|
||||
elseif($opc === 0xAC) // 1010_1100
|
||||
printf('LDY abs%s', PHP_EOL);
|
||||
elseif($opc === 0xAD) // 1010_1101
|
||||
printf('LDA abs%s', PHP_EOL);
|
||||
elseif($opc === 0xAE) // 1010_1110
|
||||
printf('LDX abs%s', PHP_EOL);
|
||||
elseif($opc === 0xCC) // 1100_1100
|
||||
printf('CPY abs%s', PHP_EOL);
|
||||
elseif($opc === 0xCD) // 1100_1101
|
||||
printf('CMP abs%s', PHP_EOL);
|
||||
elseif($opc === 0xCE) // 1100_1110
|
||||
printf('DEC abs%s', PHP_EOL);
|
||||
elseif($opc === 0xED) // 1110_1101
|
||||
printf('SBC abs%s', PHP_EOL);
|
||||
elseif($opc === 0xEC) // 1110_1100
|
||||
printf('CPX abs%s', PHP_EOL);
|
||||
elseif($opc === 0xEE) // 1110_1110
|
||||
printf('INC abs%s', PHP_EOL);
|
||||
|
||||
// ??? zp
|
||||
elseif($opc === 0x05) // 0000_0101
|
||||
printf('ORA zp%s', PHP_EOL);
|
||||
elseif($opc === 0x06) // 0000_0110
|
||||
printf('ASL zp%s', PHP_EOL);
|
||||
elseif($opc === 0x24) // 0010_0100
|
||||
printf('BIT zp%s', PHP_EOL);
|
||||
elseif($opc === 0x25) // 0010_0101
|
||||
printf('AND zp%s', PHP_EOL);
|
||||
elseif($opc === 0x26) // 0010_0110
|
||||
printf('ROL zp%s', PHP_EOL);
|
||||
elseif($opc === 0x65) // 0110_0101
|
||||
printf('ADC zp%s', PHP_EOL);
|
||||
elseif($opc === 0xC5) // 1100_0101
|
||||
printf('CMP zp%s', PHP_EOL);
|
||||
elseif($opc === 0xE4) // 1110_0100
|
||||
printf('CPX zp%s', PHP_EOL);
|
||||
elseif($opc === 0xC4) // 1100_0100
|
||||
printf('CPY zp%s', PHP_EOL);
|
||||
elseif($opc === 0xC6) // 1100_0110
|
||||
printf('DEC zp%s', PHP_EOL);
|
||||
elseif($opc === 0x45) // 0100_0101
|
||||
printf('EOR zp%s', PHP_EOL);
|
||||
elseif($opc === 0xE6) // 1110_0110
|
||||
printf('INC zp%s', PHP_EOL);
|
||||
elseif($opc === 0xA5) // 1010_0101
|
||||
printf('LDA zp%s', PHP_EOL);
|
||||
elseif($opc === 0xA6) // 1010_0110
|
||||
printf('LDX zp%s', PHP_EOL);
|
||||
elseif($opc === 0xA4) // 1010_0100
|
||||
printf('LDY zp%s', PHP_EOL);
|
||||
elseif($opc === 0x46) // 0010_0110
|
||||
printf('LSR zp%s', PHP_EOL);
|
||||
elseif($opc === 0x66) // 0110_0110
|
||||
printf('ROR zp%s', PHP_EOL);
|
||||
elseif($opc === 0xE5) // 1110_0101
|
||||
printf('SBC zp%s', PHP_EOL);
|
||||
elseif($opc === 0x84) // 1000_0100
|
||||
printf('STY zp%s', PHP_EOL);
|
||||
elseif($opc === 0x85) // 1000_0101
|
||||
printf('STA zp%s', PHP_EOL);
|
||||
elseif($opc === 0x86) // 1000_0110
|
||||
printf('STX zp%s', PHP_EOL);
|
||||
|
||||
// ??? abs,X
|
||||
elseif($opc === 0x7D) // 0111_1101
|
||||
printf('ADC abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x3D) // 0011_1101
|
||||
printf('AND abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x1E) // 0001_1110
|
||||
printf('ASL abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0xDD) // 1101_1101
|
||||
printf('CMP abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0xDE) // 1101_1110
|
||||
printf('DEC abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x5D) // 0101_1101
|
||||
printf('EOR abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0xFE) // 1111_1110
|
||||
printf('INC abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0xBD) // 1011_1101
|
||||
printf('LDA abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0xBC) // 1011_1100
|
||||
printf('LDY abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x5E) // 0101_1110
|
||||
printf('LSR abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x1D) // 0001_1101
|
||||
printf('ORA abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x3E) // 0011_1110
|
||||
printf('ROL abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x7E) // 0111_1110
|
||||
printf('ROR abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0xFD) // 1111_1101
|
||||
printf('SBC abs,X%s', PHP_EOL);
|
||||
elseif($opc === 0x9D) // 1001_1101
|
||||
printf('STA abs,X%s', PHP_EOL);
|
||||
|
||||
// ??? abs,Y
|
||||
elseif($opc === 0x79) // 0111_1001
|
||||
printf('ADC abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0x39) // 0011_1001
|
||||
printf('AND abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0xD9) // 1101_1001
|
||||
printf('CMP abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0x59) // 0101_1001
|
||||
printf('EOR abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0xB9) // 1011_1001
|
||||
printf('LDA abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0xBE) // 1011_1110
|
||||
printf('LDX abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0x19) // 0001_1001
|
||||
printf('ORA abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0xF9) // 1111_1001
|
||||
printf('SBC abs,Y%s', PHP_EOL);
|
||||
elseif($opc === 0x99) // 1001_1001
|
||||
printf('STA abs,Y%s', PHP_EOL);
|
||||
|
||||
// ??? zp,X
|
||||
elseif($opc === 0x75) // 0111_0101
|
||||
printf('ADC zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x35) // 0011_0101
|
||||
printf('AND zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x16) // 0001_0110
|
||||
printf('ASL zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0xD5) // 1101_0101
|
||||
printf('CMP zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0xD6) // 1101_0110
|
||||
printf('DEC zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x55) // 0101_0101
|
||||
printf('EOR zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0xF6) // 1111_0110
|
||||
printf('INC zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0xB5) // 1011_0101
|
||||
printf('LDA zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0xB4) // 1011_0100
|
||||
printf('LDY zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x56) // 0101_0110
|
||||
printf('LSR zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x15) // 0001_0101
|
||||
printf('ORA zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x36) // 0011_0110
|
||||
printf('ROL zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x76) // 0111_0110
|
||||
printf('ROR zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0xF5) // 1111_0101
|
||||
printf('SBC zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x95) // 1001_0101
|
||||
printf('STA zp,X%s', PHP_EOL);
|
||||
elseif($opc === 0x94) // 1001_0100
|
||||
printf('STY zp,X%s', PHP_EOL);
|
||||
|
||||
// ??? zp,Y
|
||||
elseif($opc === 0xB6) // 1101_0110
|
||||
printf('LDX zp,Y%s', PHP_EOL);
|
||||
elseif($opc === 0x96) // 1001_0110
|
||||
printf('STX zp,Y%s', PHP_EOL);
|
||||
|
||||
// ??? (zp,X)
|
||||
elseif($opc === 0x61) // 0110_0001
|
||||
printf('ADC (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0x21) // 0010_0001
|
||||
printf('AND (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0xC1) // 1100_0001
|
||||
printf('CMP (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0x41) // 0100_0001
|
||||
printf('EOR (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0xA1) // 1010_0001
|
||||
printf('LDA (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0x01) // 0000_0001
|
||||
printf('ORA (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0xE1) // 1110_0001
|
||||
printf('SBC (zp,X)%s', PHP_EOL);
|
||||
elseif($opc === 0x81) // 1000_0001
|
||||
printf('STA (zp,X)%s', PHP_EOL);
|
||||
|
||||
// ??? (zp),Y
|
||||
elseif($opc === 0x71) // 0111_0001
|
||||
printf('ADC (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0x31) // 0011_0001
|
||||
printf('AND (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0xD1) // 1101_0001
|
||||
printf('CMP (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0x51) // 0101_0001
|
||||
printf('EOR (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0xB1) // 1011_0001
|
||||
printf('LDA (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0x11) // 0001_0001
|
||||
printf('ORA (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0xF1) // 1111_0001
|
||||
printf('SBC (zp),Y%s', PHP_EOL);
|
||||
elseif($opc === 0x91) // 1001_0001
|
||||
printf('STA (zp),Y%s', PHP_EOL);
|
||||
}
|
||||
} finally {
|
||||
return $cycles;
|
||||
}
|
||||
}
|
||||
}
|
10
src/CpuInterruptRequest.php
Normal file
10
src/CpuInterruptRequest.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
enum CpuInterruptRequest {
|
||||
case None;
|
||||
case Reset;
|
||||
case Break;
|
||||
case Maskable;
|
||||
case NonMaskable;
|
||||
}
|
20
src/CpuIo.php
Normal file
20
src/CpuIo.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
interface CpuIo {
|
||||
/**
|
||||
* @param int<0, 65535> $addr
|
||||
* @throws InvalidArgumentException If $addr is outside of the specified range.
|
||||
* @return int<0, 255>
|
||||
*/
|
||||
function read(int $addr): int;
|
||||
|
||||
/**
|
||||
* @param int<0, 65535> $addr
|
||||
* @param int<0, 255> $value
|
||||
* @throws InvalidArgumentException If $addr or $value is outside of the specified ranges.
|
||||
*/
|
||||
function write(int $addr, int $value): void;
|
||||
}
|
126
src/CpuState.php
Normal file
126
src/CpuState.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
use Stringable;
|
||||
|
||||
class CpuState implements Stringable {
|
||||
public const int P_CARRY = 0x01;
|
||||
public const int P_ZERO = 0x02;
|
||||
public const int P_IRQD = 0x04;
|
||||
public const int P_DECIMAL = 0x08;
|
||||
public const int P_BREAK = 0x10;
|
||||
public const int P_OVERFLOW = 0x40;
|
||||
public const int P_NEGATIVE = 0x80;
|
||||
|
||||
public int $a = 0 {
|
||||
set { $this->a = $value & 0xFF; }
|
||||
}
|
||||
|
||||
public int $x = 0 {
|
||||
set { $this->x = $value & 0xFF; }
|
||||
}
|
||||
|
||||
public int $y = 0 {
|
||||
set { $this->y = $value & 0xFF; }
|
||||
}
|
||||
|
||||
public int $sPage = 0x0100 {
|
||||
set { $this->sPage = $value & 0xFF00; }
|
||||
}
|
||||
|
||||
public int $s = 0xFF {
|
||||
set { $this->s = $value & 0xFF; }
|
||||
}
|
||||
|
||||
public int $pc = 0 {
|
||||
set { $this->pc = $value & 0xFFFF; }
|
||||
}
|
||||
|
||||
public int $p = 0 {
|
||||
set { $this->p = $value & 0xFF; }
|
||||
}
|
||||
|
||||
public bool $pCarry {
|
||||
get { return ($this->p & self::P_CARRY) > 0; }
|
||||
set {
|
||||
if($value)
|
||||
$this->p |= self::P_CARRY;
|
||||
else
|
||||
$this->p &= ~self::P_CARRY;
|
||||
}
|
||||
}
|
||||
|
||||
public bool $pZero {
|
||||
get { return ($this->p & self::P_ZERO) > 0; }
|
||||
set {
|
||||
if($value)
|
||||
$this->p |= self::P_ZERO;
|
||||
else
|
||||
$this->p &= ~self::P_ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
public bool $pIrqDisable {
|
||||
get { return ($this->p & self::P_IRQD) > 0; }
|
||||
set {
|
||||
if($value)
|
||||
$this->p |= self::P_IRQD;
|
||||
else
|
||||
$this->p &= ~self::P_IRQD;
|
||||
}
|
||||
}
|
||||
|
||||
public bool $pDecimalMode {
|
||||
get { return ($this->p & self::P_DECIMAL) > 0; }
|
||||
set {
|
||||
if($value)
|
||||
$this->p |= self::P_DECIMAL;
|
||||
else
|
||||
$this->p &= ~self::P_DECIMAL;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if($value)
|
||||
$this->p |= self::P_OVERFLOW;
|
||||
else
|
||||
$this->p &= ~self::P_OVERFLOW;
|
||||
}
|
||||
}
|
||||
|
||||
public bool $pNegative {
|
||||
get { return ($this->p & self::P_NEGATIVE) > 0; }
|
||||
set {
|
||||
if($value)
|
||||
$this->p |= self::P_NEGATIVE;
|
||||
else
|
||||
$this->p &= ~self::P_NEGATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return sprintf(
|
||||
'PC$%04X / A$%02X / X$%02X / Y$%02X / S$%04X / P%s%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' : '-',
|
||||
$this->pCarry ? 'c' : '-',
|
||||
);
|
||||
}
|
||||
}
|
61
src/FileCpuIo.php
Normal file
61
src/FileCpuIo.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Aiko;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
// TODO: implement a streamWrapper that supports overlaying
|
||||
class FileCpuIo implements CpuIo {
|
||||
/** @var resource */
|
||||
private $file;
|
||||
|
||||
/** @param resource $file */
|
||||
public function __construct($file) {
|
||||
if(!is_resource($file))
|
||||
throw new InvalidArgumentException('$file must be a file resource');
|
||||
|
||||
$temp = fopen('php://memory', 'rb+');
|
||||
if($temp === false)
|
||||
throw new RuntimeException('could not open a memory stream');
|
||||
|
||||
$read = fread($file, 0x10000);
|
||||
fwrite($temp, $read);
|
||||
$this->file = $temp;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
fclose($this->file);
|
||||
}
|
||||
|
||||
public static function open(string $path): FileCpuIo {
|
||||
$file = fopen($path, 'rb');
|
||||
if($file === false)
|
||||
throw new RuntimeException('could not open $path');
|
||||
|
||||
try {
|
||||
return new FileCpuIo($file);
|
||||
} finally {
|
||||
fclose($file);
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws UnexpectedValueException if $readFunc is outside of the expected range. */
|
||||
public function read(int $addr): int {
|
||||
printf(' <- $%04X%s', $addr, PHP_EOL);
|
||||
|
||||
fseek($this->file, $addr);
|
||||
|
||||
$char = fgetc($this->file);
|
||||
if($char === false)
|
||||
throw new UnexpectedValueException('failed to read offset');
|
||||
|
||||
return ord($char);
|
||||
}
|
||||
|
||||
public function write(int $addr, int $num): void {
|
||||
printf(' -> $%04X = $%02X %s', $addr, $num, PHP_EOL);
|
||||
|
||||
fseek($this->file, $addr);
|
||||
fwrite($this->file, chr($num));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue