752 lines
21 KiB
D
752 lines
21 KiB
D
/+
|
|
+ d6502/cpu.d
|
|
+
|
|
+ Copyright: 2007 Gerald Stocker
|
|
+
|
|
+ This file is part of twoapple-reboot.
|
|
+
|
|
+ twoapple-reboot is free software; you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation; either version 2 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ twoapple-reboot is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with twoapple-reboot; if not, write to the Free Software
|
|
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
+/
|
|
|
|
module d6502.cpu;
|
|
|
|
import d6502.base;
|
|
|
|
class Cpu(bool strict, bool cumulative) : CpuBase!(strict, cumulative)
|
|
{
|
|
static string InitOpcodes()
|
|
{
|
|
string initCalls;
|
|
for (int op = 0; op < 256; ++op)
|
|
{
|
|
initCalls ~= "opcodes[0x" ~ hexByte(op) ~ "] = &opcode" ~
|
|
hexByte(op) ~ ";\n";
|
|
}
|
|
return initCalls;
|
|
}
|
|
|
|
this()
|
|
{
|
|
mixin(InitOpcodes());
|
|
flag = new StatusRegister();
|
|
}
|
|
|
|
const ushort STACK_BASE = 0x0100;
|
|
const ushort NMI_VECTOR = 0xFFFA;
|
|
const ushort RESET_VECTOR = 0xFFFC;
|
|
const ushort IRQ_VECTOR = 0xFFFE;
|
|
|
|
void delegate()[256] opcodes;
|
|
bool continueExecution;
|
|
static if (cumulative) int totalCycles;
|
|
|
|
|
|
debug(disassemble)
|
|
{
|
|
import hacking.debugger;
|
|
import std.stdio;
|
|
}
|
|
final override void run(bool continuous)
|
|
{
|
|
assert ((memoryRead !is null) && (memoryWrite !is null));
|
|
assert (tick !is null);
|
|
|
|
continueExecution = continuous;
|
|
do
|
|
{
|
|
if (signalActive) handleSignals();
|
|
|
|
static if (cumulative) totalCycles = 0;
|
|
opcodePC = programCounter;
|
|
opcode = read(programCounter++);
|
|
|
|
/+ TODO: call sync delegate +/
|
|
|
|
opcodes[opcode]();
|
|
debug(disassemble)
|
|
{
|
|
writefln(Debugger.disassemble(this, cmosMap)
|
|
~ Debugger.displayRegisters(this));
|
|
}
|
|
} while (continueExecution);
|
|
}
|
|
|
|
final override void stop()
|
|
{
|
|
continueExecution = false;
|
|
}
|
|
|
|
final override void resetLow()
|
|
{
|
|
resetActive = signalActive = true;
|
|
}
|
|
|
|
final override void nmiLow(bool signalLow)
|
|
{
|
|
nmiActive = signalLow;
|
|
if (!signalLow) nmiArmed = true;
|
|
signalActive = testSignals();
|
|
}
|
|
|
|
final override void irqLow(bool signalLow)
|
|
{
|
|
irqActive = signalLow;
|
|
signalActive = testSignals();
|
|
}
|
|
|
|
final bool testSignals()
|
|
{
|
|
return (resetActive || nmiActive || irqActive);
|
|
}
|
|
|
|
final void handleSignals()
|
|
{
|
|
bool checkNMI()
|
|
{
|
|
if (nmiActive && nmiArmed)
|
|
{
|
|
nmiArmed = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (resetActive) doReset();
|
|
else if (checkNMI()) do_IRQ_or_NMI(NMI_VECTOR);
|
|
else if ((!flag.interrupt) && irqActive) do_IRQ_or_NMI(IRQ_VECTOR);
|
|
}
|
|
|
|
void do_IRQ_or_NMI(ushort vector)
|
|
{
|
|
doInterrupt(vector, (flag.toByte() & ~0x10));
|
|
}
|
|
|
|
final void doInterrupt(ushort vector, ubyte statusByte)
|
|
{
|
|
pushWord(programCounter);
|
|
push(statusByte);
|
|
flag.interrupt = true;
|
|
programCounter = readWord(vector, cast(ushort)(vector + 1));
|
|
static if (cumulative) tick(totalCycles);
|
|
}
|
|
|
|
void doReset()
|
|
{
|
|
static if (cumulative)
|
|
{
|
|
totalCycles += 2;
|
|
}
|
|
else
|
|
{
|
|
tick(); tick();
|
|
}
|
|
|
|
peek(STACK_BASE + stackPointer);
|
|
--stackPointer;
|
|
peek(STACK_BASE + stackPointer);
|
|
--stackPointer;
|
|
peek(STACK_BASE + stackPointer);
|
|
--stackPointer;
|
|
|
|
flag.interrupt = true;
|
|
resetActive = false;
|
|
signalActive = testSignals();
|
|
|
|
programCounter = readWord(RESET_VECTOR, RESET_VECTOR + 1);
|
|
static if (cumulative) tick(totalCycles);
|
|
}
|
|
|
|
final ubyte read(ushort addr)
|
|
{
|
|
static if (cumulative) ++totalCycles;
|
|
else tick();
|
|
return memoryRead(addr);
|
|
}
|
|
|
|
final void write(ushort addr, ubyte val)
|
|
{
|
|
static if (cumulative) ++totalCycles;
|
|
else tick();
|
|
memoryWrite(addr, val);
|
|
}
|
|
|
|
final void peek(ushort addr)
|
|
{
|
|
static if (cumulative) ++totalCycles;
|
|
else tick();
|
|
static if (strict) memoryRead(addr);
|
|
}
|
|
|
|
final void poke(ushort addr, ubyte val)
|
|
{
|
|
static if (cumulative) ++totalCycles;
|
|
else tick();
|
|
static if (strict) memoryWrite(addr, val);
|
|
}
|
|
|
|
final ubyte readFinal(ushort addr)
|
|
{
|
|
static if (cumulative) tick(++totalCycles);
|
|
else
|
|
{
|
|
tick();
|
|
}
|
|
return memoryRead(addr);
|
|
}
|
|
|
|
final void writeFinal(ushort addr, ubyte val)
|
|
{
|
|
static if (cumulative) tick(++totalCycles);
|
|
else
|
|
{
|
|
tick();
|
|
}
|
|
memoryWrite(addr, val);
|
|
}
|
|
|
|
final ushort readWord(ushort addrLo, ushort addrHi)
|
|
{
|
|
ushort word = read(addrLo);
|
|
return word | (read(addrHi) << 8);
|
|
}
|
|
|
|
final void push(ubyte val)
|
|
{
|
|
write((STACK_BASE + stackPointer), val);
|
|
--stackPointer;
|
|
/+ TODO: call stack overflow delegate +/
|
|
}
|
|
|
|
|
|
final void pushWord(ushort val)
|
|
{
|
|
push(val >> 8);
|
|
push(val & 0xFF);
|
|
}
|
|
|
|
final ubyte readStack()
|
|
{
|
|
++stackPointer;
|
|
/+ TODO: call stack underflow delegate +/
|
|
return read(STACK_BASE + stackPointer);
|
|
}
|
|
|
|
final ubyte pull()
|
|
{
|
|
peek(STACK_BASE + stackPointer);
|
|
return readStack();
|
|
}
|
|
|
|
final ushort pullWord()
|
|
{
|
|
ushort word = pull();
|
|
return word | (readStack() << 8);
|
|
}
|
|
|
|
final ubyte readByteOperand()
|
|
{
|
|
return (operand1 = read(programCounter++));
|
|
}
|
|
|
|
final ushort readWordOperand()
|
|
{
|
|
operand1 = read(programCounter++);
|
|
operand2 = read(programCounter++);
|
|
return (operand1 | (operand2 << 8));
|
|
}
|
|
|
|
ushort* spuriousAddress;
|
|
ushort badAddress, baseAddress, primaryAddress;
|
|
ubyte readVal, writeVal;
|
|
|
|
final ushort tryShortcut(bool noShortcut, ushort goodAddress)
|
|
{
|
|
badAddress = (baseAddress & 0xFF00) | cast(ubyte)goodAddress;
|
|
if (badAddress != goodAddress)
|
|
peek(*spuriousAddress);
|
|
else if (noShortcut)
|
|
peek(goodAddress);
|
|
return goodAddress;
|
|
}
|
|
|
|
final void addrRelative(byte offset)
|
|
{
|
|
peek(programCounter);
|
|
baseAddress = programCounter;
|
|
programCounter = tryShortcut(false,
|
|
cast(ushort)(programCounter + offset));
|
|
}
|
|
|
|
final void addrZeropage()
|
|
{
|
|
primaryAddress = readByteOperand();
|
|
}
|
|
|
|
final void addrAbsolute()
|
|
{
|
|
primaryAddress = readWordOperand();
|
|
}
|
|
|
|
final void addrZeropageX()
|
|
{
|
|
baseAddress = badAddress = readByteOperand();
|
|
peek(*spuriousAddress);
|
|
primaryAddress = cast(ubyte)(baseAddress + xIndex);
|
|
}
|
|
|
|
final void addrZeropageY()
|
|
{
|
|
baseAddress = badAddress = readByteOperand();
|
|
peek(*spuriousAddress);
|
|
primaryAddress = cast(ubyte)(baseAddress + yIndex);
|
|
}
|
|
|
|
final void addrIndirectX()
|
|
{
|
|
baseAddress = badAddress = readByteOperand();
|
|
peek(*spuriousAddress);
|
|
ushort vector = cast(ubyte)(baseAddress + xIndex);
|
|
primaryAddress = readWord(vector, cast(ubyte)(vector + 1));
|
|
}
|
|
|
|
final void addrAbsoluteX(bool write)
|
|
{
|
|
baseAddress = readWordOperand();
|
|
primaryAddress = tryShortcut(write,
|
|
cast(ushort)(baseAddress + xIndex));
|
|
}
|
|
|
|
final void addrAbsoluteY(bool write)
|
|
{
|
|
baseAddress = readWordOperand();
|
|
primaryAddress = tryShortcut(write,
|
|
cast(ushort)(baseAddress + yIndex));
|
|
}
|
|
|
|
final void addrIndirectY(bool write)
|
|
{
|
|
ubyte vector = readByteOperand();
|
|
baseAddress = readWord(vector, cast(ubyte)(vector + 1));
|
|
primaryAddress = tryShortcut(write,
|
|
cast(ushort)(baseAddress + yIndex));
|
|
}
|
|
|
|
void dec_addWithCarry(ubyte val)
|
|
{
|
|
int a = accumulator;
|
|
int al = (a & 0x0F) + (val & 0x0F) + flag.carry;
|
|
if (al >= 0x0A)
|
|
al = ((al + 0x06) & 0x0F) + 0x10;
|
|
a = (a & 0xF0) + (val & 0xF0) + al;
|
|
|
|
flag.negative_ = cast(ubyte)a;
|
|
flag.zero_ = cast(ubyte)(accumulator + val + flag.carry);
|
|
flag.overflow =
|
|
(!((accumulator ^ val) & 0x80)) && ((val ^ a) & 0x80);
|
|
|
|
if (a >= 0xA0)
|
|
a = a + 0x60;
|
|
|
|
flag.carry = (a >= 0x100);
|
|
accumulator = cast(ubyte)a;
|
|
}
|
|
|
|
void dec_subWithCarry(ubyte val)
|
|
{
|
|
int a = accumulator;
|
|
int al = (a & 0x0F) - (val & 0x0F) - !flag.carry;
|
|
if (al < 0)
|
|
al = ((al - 0x06) & 0x0F) - 0x10;
|
|
a = (a & 0xF0) - (val & 0xF0) + al;
|
|
if (a < 0)
|
|
a = a - 0x60;
|
|
|
|
uint diff = accumulator - val - !flag.carry;
|
|
flag.overflow =
|
|
((accumulator ^ diff) & 0x80) &&
|
|
((accumulator ^ val) & 0x80);
|
|
flag.carry = (diff < 0x100);
|
|
flag.zero_ = flag.negative_ = cast(ubyte)diff;
|
|
|
|
accumulator = cast(ubyte)a;
|
|
}
|
|
|
|
final void hex_addWithCarry(ubyte val)
|
|
{
|
|
uint sum = accumulator + val + flag.carry;
|
|
|
|
flag.overflow =
|
|
(!((accumulator ^ val) & 0x80)) && ((val ^ sum) & 0x80);
|
|
flag.carry = (sum > 0xFF);
|
|
|
|
flag.zero_ = flag.negative_ = (accumulator = cast(ubyte)sum);
|
|
}
|
|
|
|
final void hex_subWithCarry(ubyte val)
|
|
{
|
|
uint diff = accumulator - val - (flag.carry ? 0 : 1);
|
|
|
|
flag.overflow =
|
|
((accumulator ^ diff) & 0x80) &&
|
|
((accumulator ^ val) & 0x80);
|
|
flag.carry = (diff < 0x100);
|
|
|
|
flag.zero_ = flag.negative_ = (accumulator = cast(ubyte)diff);
|
|
}
|
|
|
|
final ubyte compare(ubyte reg, ubyte val)
|
|
{
|
|
flag.carry = (reg >= val);
|
|
return cast(ubyte)(reg - val);
|
|
}
|
|
|
|
final void bitTest(ubyte val)
|
|
{
|
|
flag.negative_ = val;
|
|
flag.zero_ = accumulator & val;
|
|
flag.overflow = ((val & 0x40) != 0);
|
|
}
|
|
|
|
final ubyte shiftLeft(ubyte val)
|
|
{
|
|
flag.carry = (val > 0x7F);
|
|
return cast(ubyte)(val << 1);
|
|
}
|
|
|
|
final ubyte rotateLeft(ubyte val)
|
|
{
|
|
bool oldCarry = flag.carry;
|
|
flag.carry = (val > 0x7F);
|
|
val = cast(ubyte)(val << 1 | (oldCarry ? 1 : 0));
|
|
return val;
|
|
}
|
|
|
|
final ubyte shiftRight(ubyte val)
|
|
{
|
|
flag.carry = ((val & 0x01) != 0);
|
|
return val >> 1;
|
|
}
|
|
|
|
final ubyte rotateRight(ubyte val)
|
|
{
|
|
bool oldCarry = flag.carry;
|
|
flag.carry = ((val & 0x01) != 0);
|
|
val = (val >> 1 | (oldCarry ? 0x80 : 0));
|
|
return val;
|
|
}
|
|
|
|
final ubyte increment(ubyte val)
|
|
{
|
|
return cast(ubyte)(val + 1);
|
|
}
|
|
|
|
final ubyte decrement(ubyte val)
|
|
{
|
|
return cast(ubyte)(val - 1);
|
|
}
|
|
|
|
static string SimpleOpcode(string name, string opcode, string action)
|
|
{
|
|
string code = "peek(programCounter);\n";
|
|
code ~= (action == "") ? "" : (action ~ ";");
|
|
static if (cumulative) code ~= "tick(totalCycles);\n";
|
|
return "override void opcode" ~ opcode ~ "()\n{\n" ~ code ~ "\n}\n";
|
|
}
|
|
|
|
static string UpdateNZ(string action)
|
|
{
|
|
return "flag.zero_ = flag.negative_ = (" ~ action ~ ");" ~ "\n";
|
|
}
|
|
|
|
static string RegisterOpcode(string name, string opcode, string action)
|
|
{
|
|
string code = "peek(programCounter);\n";
|
|
code ~= UpdateNZ(action);
|
|
static if (cumulative) code ~= "tick(totalCycles);\n";
|
|
return "override void opcode" ~ opcode ~ "()\n{\n" ~ code ~ "}\n";
|
|
}
|
|
|
|
static string BranchOpcode(string name, string opcode, string action)
|
|
{
|
|
string code = "readByteOperand();\n" ~
|
|
"if (" ~ action ~ ") addrRelative(cast(byte)operand1);\n";
|
|
static if (cumulative) code ~= "tick(totalCycles);\n";
|
|
return "override void opcode" ~ opcode ~ "()\n{\n" ~ code ~ "}\n";
|
|
}
|
|
|
|
static string Type1Address(string name, string rw, int[] opcodes)
|
|
{
|
|
string type = (rw == "Write") ? "true" : "false";
|
|
string modes = "[[\"" ~ name ~ "\", \"" ~ rw ~ "\"], \n";
|
|
for (int op = 0; op < opcodes.length; ++op)
|
|
{
|
|
int opcode = opcodes[op];
|
|
modes ~= "[\"" ~ hexByte(opcode) ~ "\", \"";
|
|
final switch ((opcode & 0b00011100) >> 2)
|
|
{
|
|
case 0:
|
|
modes ~= "IndirectX()";
|
|
break;
|
|
case 1:
|
|
modes ~= "Zeropage()";
|
|
break;
|
|
case 2:
|
|
modes ~= "Immediate";
|
|
break;
|
|
case 3:
|
|
modes ~= "Absolute()";
|
|
break;
|
|
case 4:
|
|
modes ~= "IndirectY("~ type ~ ")";
|
|
break;
|
|
case 5:
|
|
modes ~= "ZeropageX()";
|
|
break;
|
|
case 6:
|
|
modes ~= "AbsoluteY(" ~ type ~ ")";
|
|
break;
|
|
case 7:
|
|
modes ~= "AbsoluteX(" ~ type ~ ")";
|
|
break;
|
|
}
|
|
modes ~= "\"]";
|
|
if (op != (opcodes.length - 1)) modes ~= ", ";
|
|
modes ~= "\n";
|
|
}
|
|
return modes ~ "]\n";
|
|
}
|
|
|
|
static string Type2Address(string name, string rw, int[] opcodes)
|
|
{
|
|
string type = (rw == "Write") ? "true" : "false";
|
|
string index = (name[2] == 'X') ? "Y" : "X";
|
|
string modes = "[[\"" ~ name ~ "\", \"" ~ rw ~ "\"], \n";
|
|
for (int op = 0; op < opcodes.length; ++op)
|
|
{
|
|
int opcode = opcodes[op];
|
|
modes ~= "[\"" ~ hexByte(opcode) ~ "\", \"";
|
|
final switch ((opcode & 0b00011100) >> 2)
|
|
{
|
|
case 0:
|
|
modes ~= "Immediate";
|
|
break;
|
|
case 1:
|
|
modes ~= "Zeropage()";
|
|
break;
|
|
case 3:
|
|
modes ~= "Absolute()";
|
|
break;
|
|
case 5:
|
|
modes ~= "Zeropage" ~ index ~ "()";
|
|
break;
|
|
case 7:
|
|
modes ~= "Absolute" ~ index ~ "(" ~ type ~ ")";
|
|
break;
|
|
}
|
|
modes ~= "\"]";
|
|
if (op != (opcodes.length - 1)) modes ~= ", ";
|
|
modes ~= "\n";
|
|
}
|
|
return modes ~ "]\n";
|
|
}
|
|
|
|
static string Opcode(string[][] details, string action)
|
|
{
|
|
string methods;
|
|
for (int op = 1; op < details.length; ++op)
|
|
{
|
|
methods ~= "override void opcode" ~ details[op][0] ~ "()\n{\n";
|
|
if (details[op][1] == "Immediate")
|
|
{
|
|
methods ~= "primaryAddress = programCounter++;\n" ~
|
|
action ~ "operand1 = readVal;\n";
|
|
}
|
|
else
|
|
{
|
|
methods ~= "addr" ~ details[op][1] ~ ";\n" ~ action;
|
|
}
|
|
methods ~= "}\n";
|
|
}
|
|
return methods;
|
|
}
|
|
|
|
static string Read(string action)
|
|
{
|
|
return UpdateNZ(action ~ " (readVal = readFinal(primaryAddress))");
|
|
}
|
|
|
|
static string Decimal(string action)
|
|
{
|
|
string code = action ~ "(readVal = readFinal(primaryAddress));\n";
|
|
return "if (flag.decimal) dec_" ~ code ~
|
|
"else hex_" ~ code;
|
|
}
|
|
|
|
static string Compare(string action)
|
|
{
|
|
return UpdateNZ("compare(" ~ action ~
|
|
", (readVal = readFinal(primaryAddress)))");
|
|
}
|
|
|
|
static string Write(string action)
|
|
{
|
|
return "writeFinal(primaryAddress, " ~ action ~ ");\n";
|
|
}
|
|
|
|
static string BitTest()
|
|
{
|
|
return "bitTest(readVal = readFinal(primaryAddress));\n";
|
|
}
|
|
|
|
mixin(SimpleOpcode("CLC", "18", "flag.carry = false"));
|
|
mixin(SimpleOpcode("SEC", "38", "flag.carry = true"));
|
|
mixin(SimpleOpcode("CLI", "58", "flag.interrupt = false"));
|
|
mixin(SimpleOpcode("SEI", "78", "flag.interrupt = true"));
|
|
mixin(SimpleOpcode("CLV", "B8", "flag.overflow = false"));
|
|
mixin(SimpleOpcode("CLD", "D8", "flag.decimal = false"));
|
|
mixin(SimpleOpcode("SED", "F8", "flag.decimal = true"));
|
|
|
|
mixin(SimpleOpcode("NOP", "EA", ""));
|
|
|
|
mixin(SimpleOpcode("PHP", "08", "push(flag.toByte())"));
|
|
mixin(SimpleOpcode("PLP", "28", "flag.fromByte(pull())"));
|
|
mixin(SimpleOpcode("PHA", "48", "push(accumulator)"));
|
|
mixin(SimpleOpcode("TXS", "9A", "stackPointer = xIndex"));
|
|
|
|
mixin(RegisterOpcode("PLA", "68", "accumulator = pull()"));
|
|
mixin(RegisterOpcode("TSX", "BA", "xIndex = stackPointer"));
|
|
|
|
mixin(RegisterOpcode("TAX", "AA", "xIndex = accumulator"));
|
|
mixin(RegisterOpcode("TXA", "8A", "accumulator = xIndex"));
|
|
mixin(RegisterOpcode("DEX", "CA", "xIndex -= 1"));
|
|
mixin(RegisterOpcode("INX", "E8", "xIndex += 1"));
|
|
mixin(RegisterOpcode("TAY", "A8", "yIndex = accumulator"));
|
|
mixin(RegisterOpcode("TYA", "98", "accumulator = yIndex"));
|
|
mixin(RegisterOpcode("DEY", "88", "yIndex -= 1"));
|
|
mixin(RegisterOpcode("INY", "C8", "yIndex += 1"));
|
|
|
|
mixin(BranchOpcode("BPL", "10", "flag.negative_ < 0x80"));
|
|
mixin(BranchOpcode("BMI", "30", "flag.negative_ > 0x7F"));
|
|
mixin(BranchOpcode("BVC", "50", "!flag.overflow"));
|
|
mixin(BranchOpcode("BVS", "70", "flag.overflow"));
|
|
mixin(BranchOpcode("BCC", "90", "!flag.carry"));
|
|
mixin(BranchOpcode("BCS", "B0", "flag.carry"));
|
|
mixin(BranchOpcode("BNE", "D0", "flag.zero_ != 0"));
|
|
mixin(BranchOpcode("BEQ", "F0", "flag.zero_ == 0"));
|
|
|
|
mixin(RegisterOpcode("ASL A", "0A",
|
|
"accumulator = shiftLeft(accumulator)"));
|
|
mixin(RegisterOpcode("ROL A", "2A",
|
|
"accumulator = rotateLeft(accumulator)"));
|
|
mixin(RegisterOpcode("LSR A", "4A",
|
|
"accumulator = shiftRight(accumulator)"));
|
|
mixin(RegisterOpcode("ROR A", "6A",
|
|
"accumulator = rotateRight(accumulator)"));
|
|
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"LDA", "Read", [0xA1, 0xA5, 0xA9, 0xAD, 0xB1, 0xB5, 0xB9, 0xBD])),
|
|
Read("accumulator =")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"ORA", "Read", [0x01, 0x05, 0x09, 0x0D, 0x11, 0x15, 0x19, 0x1D])),
|
|
Read("accumulator |=")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"AND", "Read", [0x21, 0x25, 0x29, 0x2D, 0x31, 0x35, 0x39, 0x3D])),
|
|
Read("accumulator &=")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"EOR", "Read", [0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D])),
|
|
Read("accumulator ^=")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"ADC", "Read", [0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D])),
|
|
Decimal("addWithCarry")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"SBC", "Read", [0xE1, 0xE5, 0xE9, 0xED, 0xF1, 0xF5, 0xF9, 0xFD])),
|
|
Decimal("subWithCarry")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"CMP", "Read", [0xC1, 0xC5, 0xC9, 0xCD, 0xD1, 0xD5, 0xD9, 0xDD])),
|
|
Compare("accumulator")));
|
|
mixin(Opcode(mixin(Type1Address(
|
|
"STA", "Write", [0x81, 0x85, 0x8D, 0x91, 0x95, 0x99, 0x9D])),
|
|
Write("accumulator")));
|
|
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"LDX", "Read", [0xA2, 0xA6, 0xAE, 0xB6, 0xBE])),
|
|
Read("xIndex =")));
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"LDY", "Read", [0xA0, 0xA4, 0xAC, 0xB4, 0xBC])),
|
|
Read("yIndex =")));
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"CPX", "Read", [0xE0, 0xE4, 0xEC])),
|
|
Compare("xIndex")));
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"CPY", "Read", [0xC0, 0xC4, 0xCC])),
|
|
Compare("yIndex")));
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"STX", "Write", [0x86, 0x8E, 0x96])),
|
|
Write("xIndex")));
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"STY", "Write", [0x84, 0x8C, 0x94])),
|
|
Write("yIndex")));
|
|
mixin(Opcode(mixin(Type2Address(
|
|
"BIT", "Read", [0x24, 0x2C])),
|
|
BitTest()));
|
|
|
|
/* BRK */
|
|
final override void opcode00()
|
|
{
|
|
peek(programCounter);
|
|
++programCounter;
|
|
doInterrupt(IRQ_VECTOR, flag.toByte());
|
|
}
|
|
|
|
/* JSR */
|
|
final override void opcode20()
|
|
{
|
|
ushort finalAddress = (operand1 = read(programCounter++));
|
|
|
|
peek(STACK_BASE + stackPointer);
|
|
pushWord(programCounter);
|
|
|
|
finalAddress |= ((operand2 = read(programCounter)) << 8);
|
|
static if (cumulative) tick(totalCycles);
|
|
programCounter = finalAddress;
|
|
}
|
|
|
|
/* RTI */
|
|
final override void opcode40()
|
|
{
|
|
peek(programCounter);
|
|
flag.fromByte(pull());
|
|
programCounter = readStack() | (readStack() << 8);
|
|
static if (cumulative) tick(totalCycles);
|
|
}
|
|
|
|
/* JMP $$$$ */
|
|
final override void opcode4C()
|
|
{
|
|
programCounter = readWordOperand();
|
|
static if (cumulative) tick(totalCycles);
|
|
}
|
|
|
|
/* RTS */
|
|
final override void opcode60()
|
|
{
|
|
peek(programCounter);
|
|
programCounter = pullWord();
|
|
peek(programCounter);
|
|
static if (cumulative) tick(totalCycles);
|
|
++programCounter;
|
|
}
|
|
}
|