initial vommit

This commit is contained in:
flash 2024-12-09 03:51:28 +00:00
commit 471377fe70
15 changed files with 809 additions and 0 deletions

8
.editorconfig Normal file
View 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
View file

@ -0,0 +1 @@
* text=auto

9
.gitignore vendored Normal file
View 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
View 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
View file

@ -0,0 +1,5 @@
# AIKO PHP
this is a 6502 emulator implemented in PHP 8.4 because uhhh
![](aiko-magipoka.gif)

BIN
aiko-magipoka.gif Normal file

Binary file not shown.

After

(image error) Size: 148 KiB

36
aiko.php Normal file
View 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
View file

@ -0,0 +1,10 @@
{
"require": {
"php": ">=8.4"
},
"autoload": {
"psr-4": {
"Aiko\\": "src"
}
}
}

20
composer.lock generated Normal file
View 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
View 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
View 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;
}
}
}

View 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
View 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
View 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
View 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));
}
}