Merge branch 'newcpu'
This commit is contained in:
commit
bcc294b888
|
@ -16,6 +16,15 @@ Build by running `make` in the `src` directory; if the dependencies aren't insta
|
||||||
make GTKD=/path/to/gtkd DERELICT=/path/to/Derelict2
|
make GTKD=/path/to/gtkd DERELICT=/path/to/Derelict2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
There are tests for the 6502/65C02 emulation:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd test
|
||||||
|
rdmd runtests.d --help
|
||||||
|
```
|
||||||
|
|
||||||
### Use
|
### Use
|
||||||
For now, see README.orig
|
For now, see README.orig
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
COMPILE_OPTS = -op -Jdata -I$(GTKD)/src \
|
COMPILE_OPTS = -op -Jdata -I$(GTKD)/src \
|
||||||
-I$(GTKD)/srcgl -I$(DERELICT)/import \
|
-I$(GTKD)/srcgl -I$(DERELICT)/import \
|
||||||
-inline -release -O -noboundscheck
|
-inline -release -O -noboundscheck \
|
||||||
|
-version=OpNestedSwitch
|
||||||
|
|
||||||
LINK_OPTS = -L-lpthread -L-lGL -L-ldl -L-lX11 \
|
LINK_OPTS = -L-lpthread -L-lGL -L-ldl -L-lX11 \
|
||||||
-L-L$(GTKD) -L-lgtkd -L-lgtkdgl \
|
-L-L$(GTKD) -L-lgtkd -L-lgtkdgl \
|
||||||
-L-L$(DERELICT)/lib -L-lDerelictSDL -L-lDerelictUtil
|
-L-L$(DERELICT)/lib -L-lDerelictSDL -L-lDerelictUtil
|
||||||
|
|
||||||
ALL_SRC = $(shell find -name "*.d" \! -path "./cpu/*")
|
ALL_SRC = $(shell find -name "*.d" \! -name "ctfe*")
|
||||||
|
|
||||||
all: ${ALL_SRC}
|
all: ${ALL_SRC}
|
||||||
dmd $(COMPILE_OPTS) ${ALL_SRC} -oftwoapple ${LINK_OPTS}
|
dmd $(COMPILE_OPTS) ${ALL_SRC} -oftwoapple ${LINK_OPTS}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,173 @@
|
||||||
|
/+
|
||||||
|
+ cpu/d6502.d
|
||||||
|
+
|
||||||
|
+ Copyright: 2012 Ed McCardell, 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 cpu.d6502;
|
||||||
|
|
||||||
|
|
||||||
|
import std.array, std.format;
|
||||||
|
|
||||||
|
import cpu.ctfe_d6502;
|
||||||
|
|
||||||
|
|
||||||
|
template is6502(T)
|
||||||
|
{
|
||||||
|
enum is6502 = __traits(getMember, T, "_chip") == "6502";
|
||||||
|
}
|
||||||
|
|
||||||
|
template is65C02(T)
|
||||||
|
{
|
||||||
|
enum is65C02 = __traits(getMember, T, "_chip") == "65C02";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class Cpu(string chip, MEM, CLK)
|
||||||
|
if (__traits(compiles, {
|
||||||
|
MEM m; ubyte val; ushort addr;
|
||||||
|
val = m.read(addr);
|
||||||
|
m.write(addr, val);
|
||||||
|
CLK c; int cycles;
|
||||||
|
version(Cumulative) c.tick(cycles);
|
||||||
|
else c.tick();
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
static assert(chip == "6502" || chip == "65C02" || chip == "65c02");
|
||||||
|
enum _isCpu = true;
|
||||||
|
enum _chip = (chip == "6502" ? "6502" : "65C02");
|
||||||
|
|
||||||
|
MEM memory;
|
||||||
|
CLK clock;
|
||||||
|
|
||||||
|
ubyte A, X, Y, S;
|
||||||
|
ushort PC;
|
||||||
|
|
||||||
|
// The status flags.
|
||||||
|
ubyte N, Z;
|
||||||
|
bool V, D, I, C;
|
||||||
|
|
||||||
|
version(OpDelegates)
|
||||||
|
{
|
||||||
|
mixin(OpArrayDef());
|
||||||
|
version(Cumulative) { int cycles; }
|
||||||
|
ushort address, base;
|
||||||
|
ubyte data;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(MEM memory, CLK clock)
|
||||||
|
{
|
||||||
|
this.memory = memory;
|
||||||
|
this.clock = clock;
|
||||||
|
|
||||||
|
version(OpDelegates) mixin(OpArrayInit());
|
||||||
|
}
|
||||||
|
|
||||||
|
final void statusFromByte(ubyte p)
|
||||||
|
{
|
||||||
|
N = p;
|
||||||
|
V = ((p & 0x40) != 0);
|
||||||
|
D = ((p & 0x08) != 0);
|
||||||
|
I = ((p & 0x04) != 0);
|
||||||
|
Z = ((p & 0x02) ? 0 : 1);
|
||||||
|
C = ((p & 0x01) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ubyte statusToByte()
|
||||||
|
{
|
||||||
|
return (C ? 0x01 : 0) |
|
||||||
|
((Z == 0) ? 0x02 : 0) |
|
||||||
|
(I ? 0x04 : 0) |
|
||||||
|
(D ? 0x08 : 0) |
|
||||||
|
0x30 | // break and reserved both set
|
||||||
|
(V ? 0x40 : 0) |
|
||||||
|
(N & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool keepRunning;
|
||||||
|
bool signalActive;
|
||||||
|
bool resetLow;
|
||||||
|
|
||||||
|
final void run(bool continuous)
|
||||||
|
{
|
||||||
|
keepRunning = continuous;
|
||||||
|
ubyte opcode;
|
||||||
|
static if (!opArray)
|
||||||
|
{
|
||||||
|
version(Cumulative) { int cycles; }
|
||||||
|
ushort address, base;
|
||||||
|
ubyte data;
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
version(Cumulative) { static if (!opArray) cycles = 1; }
|
||||||
|
else { clock.tick(); }
|
||||||
|
if (signalActive) handleSignals();
|
||||||
|
opcode = memory.read(PC++);
|
||||||
|
mixin(OpExecute(_chip));
|
||||||
|
} while (keepRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: irq/nmi
|
||||||
|
void handleSignals()
|
||||||
|
{
|
||||||
|
if (resetLow) doReset();
|
||||||
|
// XXX fix when more than one signal
|
||||||
|
signalActive = resetLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doReset()
|
||||||
|
{
|
||||||
|
mixin(Tick() ~ Tick() ~
|
||||||
|
Peek(STACK) ~ DecSP() ~
|
||||||
|
Peek(STACK) ~ DecSP() ~
|
||||||
|
Peek(STACK) ~ DecSP());
|
||||||
|
|
||||||
|
I = true;
|
||||||
|
resetLow = false;
|
||||||
|
|
||||||
|
mixin(ReadWord(_PC, "RESET_VECTOR") ~
|
||||||
|
Done());
|
||||||
|
}
|
||||||
|
|
||||||
|
version(OpDelegates) mixin (OpMethods(_chip));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum ushort IRQ_VECTOR = 0xFFFE;
|
||||||
|
enum ushort RESET_VECTOR = 0xFFFC;
|
||||||
|
|
||||||
|
|
||||||
|
//alias Cpu!("6502", false, false) T1;
|
||||||
|
//alias Cpu!("6502", false, true) T2;
|
||||||
|
//alias Cpu!("6502", true, false) T3;
|
||||||
|
//alias Cpu!("6502", true, true) T4;
|
||||||
|
//alias Cpu!("65C02", false, false) T5;
|
||||||
|
//alias Cpu!("65C02", false, true) T6;
|
||||||
|
//alias Cpu!("65C02", true, false) T7;
|
||||||
|
//alias Cpu!("65C02", true, true) T8;
|
||||||
|
|
||||||
|
/+
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
import std.stdio;
|
||||||
|
writeln(OpBody(0x11, "6502", true, false));
|
||||||
|
}
|
||||||
|
+/
|
|
@ -0,0 +1,190 @@
|
||||||
|
module cpu.data_d6502;
|
||||||
|
|
||||||
|
|
||||||
|
// Opcode names.
|
||||||
|
immutable OP_NAMES_6502 = [
|
||||||
|
"BRK", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO",
|
||||||
|
"PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO",
|
||||||
|
"BPL", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO",
|
||||||
|
"CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO",
|
||||||
|
"JSR", "AND", "KIL", "RLA", "BIT", "AND", "ROL", "RLA",
|
||||||
|
"PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA",
|
||||||
|
"BMI", "AND", "KIL", "RLA", "NOP", "AND", "ROL", "RLA",
|
||||||
|
"SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA",
|
||||||
|
"RTI", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE",
|
||||||
|
"PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE",
|
||||||
|
"BVC", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE",
|
||||||
|
"CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE",
|
||||||
|
"RTS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA",
|
||||||
|
"PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA",
|
||||||
|
"BVS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA",
|
||||||
|
"SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA",
|
||||||
|
"NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX",
|
||||||
|
"DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX",
|
||||||
|
"BCC", "STA", "KIL", "AHX", "STY", "STA", "STX", "SAX",
|
||||||
|
"TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX",
|
||||||
|
"LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX",
|
||||||
|
"TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX",
|
||||||
|
"BCS", "LDA", "KIL", "LAX", "LDY", "LDA", "LDX", "LAX",
|
||||||
|
"CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX",
|
||||||
|
"CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP",
|
||||||
|
"INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP",
|
||||||
|
"BNE", "CMP", "KIL", "DCP", "NOP", "CMP", "DEC", "DCP",
|
||||||
|
"CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP",
|
||||||
|
"CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC",
|
||||||
|
"INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC",
|
||||||
|
"BEQ", "SBC", "KIL", "ISC", "NOP", "SBC", "INC", "ISC",
|
||||||
|
"SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC"
|
||||||
|
];
|
||||||
|
|
||||||
|
immutable OP_NAMES_65C02 = [
|
||||||
|
"BRK", "ORA", "NOP", "NOP", "TSB", "ORA", "ASL", "NOP",
|
||||||
|
"PHP", "ORA", "ASL", "NOP", "TSB", "ORA", "ASL", "NOP",
|
||||||
|
"BPL", "ORA", "ORA", "NOP", "TRB", "ORA", "ASL", "NOP",
|
||||||
|
"CLC", "ORA", "INC", "NOP", "TRB", "ORA", "ASL", "NOP",
|
||||||
|
"JSR", "AND", "NOP", "NOP", "BIT", "AND", "ROL", "NOP",
|
||||||
|
"PLP", "AND", "ROL", "NOP", "BIT", "AND", "ROL", "NOP",
|
||||||
|
"BMI", "AND", "AND", "NOP", "BIT", "AND", "ROL", "NOP",
|
||||||
|
"SEC", "AND", "DEC", "NOP", "BIT", "AND", "ROL", "NOP",
|
||||||
|
"RTI", "EOR", "NOP", "NOP", "NOP", "EOR", "LSR", "NOP",
|
||||||
|
"PHA", "EOR", "LSR", "NOP", "JMP", "EOR", "LSR", "NOP",
|
||||||
|
"BVC", "EOR", "EOR", "NOP", "NOP", "EOR", "LSR", "NOP",
|
||||||
|
"CLI", "EOR", "PHY", "NOP", "NOP", "EOR", "LSR", "NOP",
|
||||||
|
"RTS", "ADC", "NOP", "NOP", "STZ", "ADC", "ROR", "NOP",
|
||||||
|
"PLA", "ADC", "ROR", "NOP", "JMP", "ADC", "ROR", "NOP",
|
||||||
|
"BVS", "ADC", "ADC", "NOP", "STZ", "ADC", "ROR", "NOP",
|
||||||
|
"SEI", "ADC", "PLY", "NOP", "JMP", "ADC", "ROR", "NOP",
|
||||||
|
"BRA", "STA", "NOP", "NOP", "STY", "STA", "STX", "NOP",
|
||||||
|
"DEY", "BIT", "TXA", "NOP", "STY", "STA", "STX", "NOP",
|
||||||
|
"BCC", "STA", "STA", "NOP", "STY", "STA", "STX", "NOP",
|
||||||
|
"TYA", "STA", "TXS", "NOP", "STZ", "STA", "STZ", "NOP",
|
||||||
|
"LDY", "LDA", "LDX", "NOP", "LDY", "LDA", "LDX", "NOP",
|
||||||
|
"TAY", "LDA", "TAX", "NOP", "LDY", "LDA", "LDX", "NOP",
|
||||||
|
"BCS", "LDA", "LDA", "NOP", "LDY", "LDA", "LDX", "NOP",
|
||||||
|
"CLV", "LDA", "TSX", "NOP", "LDY", "LDA", "LDX", "NOP",
|
||||||
|
"CPY", "CMP", "NOP", "NOP", "CPY", "CMP", "DEC", "NOP",
|
||||||
|
"INY", "CMP", "DEX", "NOP", "CPY", "CMP", "DEC", "NOP",
|
||||||
|
"BNE", "CMP", "CMP", "NOP", "NOP", "CMP", "DEC", "NOP",
|
||||||
|
"CLD", "CMP", "PHX", "NOP", "NOP", "CMP", "DEC", "NOP",
|
||||||
|
"CPX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "NOP",
|
||||||
|
"INX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "NOP",
|
||||||
|
"BEQ", "SBC", "SBC", "NOP", "NOP", "SBC", "INC", "NOP",
|
||||||
|
"SED", "SBC", "PLX", "NOP", "NOP", "SBC", "INC", "NOP"
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// Addressing modes.
|
||||||
|
|
||||||
|
enum { IMP, IMM, ZP, ZPX, ZPY, IZX, IZY, ABS, ABX, ABY, IND, REL,
|
||||||
|
ZPI, ABI, NP1, NP8, KIL }
|
||||||
|
|
||||||
|
immutable ADDR_MODES_6502 = [
|
||||||
|
IMP, IZX, KIL, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPX, ZPX,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABX, ABX,
|
||||||
|
ABS, IZX, KIL, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPX, ZPX,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABX, ABX,
|
||||||
|
IMP, IZX, KIL, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPX, ZPX,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABX, ABX,
|
||||||
|
IMP, IZX, KIL, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, IND, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPX, ZPX,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABX, ABX,
|
||||||
|
IMM, IZX, IMM, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPY, ZPY,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABY, ABY,
|
||||||
|
IMM, IZX, IMM, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPY, ZPY,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABY, ABY,
|
||||||
|
IMM, IZX, IMM, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPX, ZPX,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABX, ABX,
|
||||||
|
IMM, IZX, IMM, IZX, ZP, ZP, ZP, ZP,
|
||||||
|
IMP, IMM, IMP, IMM, ABS, ABS, ABS, ABS,
|
||||||
|
REL, IZY, KIL, IZY, ZPX, ZPX, ZPX, ZPX,
|
||||||
|
IMP, ABY, IMP, ABY, ABX, ABX, ABX, ABX
|
||||||
|
];
|
||||||
|
|
||||||
|
immutable ADDR_MODES_65C02 = [
|
||||||
|
IMP, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZP, ZPX, ZPX, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABS, ABX, ABX, NP1,
|
||||||
|
ABS, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPX, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABX, ABX, ABX, NP1,
|
||||||
|
IMP, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPX, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, NP8, ABX, ABX, NP1,
|
||||||
|
IMP, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, IND, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPX, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABI, ABX, ABX, NP1,
|
||||||
|
REL, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPY, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABX, ABX, ABX, NP1,
|
||||||
|
IMM, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPY, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABX, ABX, ABY, NP1,
|
||||||
|
IMM, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPX, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABX, ABX, ABX, NP1,
|
||||||
|
IMM, IZX, IMM, NP1, ZP, ZP, ZP, NP1,
|
||||||
|
IMP, IMM, IMP, NP1, ABS, ABS, ABS, NP1,
|
||||||
|
REL, IZY, ZPI, NP1, ZPX, ZPX, ZPX, NP1,
|
||||||
|
IMP, ABY, IMP, NP1, ABX, ABX, ABX, NP1
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// Page-crossing extra cycles.
|
||||||
|
|
||||||
|
immutable EXTRA_CYCLES_6502 = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
immutable EXTRA_CYCLES_65C02 = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||||
|
];
|
152
src/d6502/base.d
152
src/d6502/base.d
|
@ -1,152 +0,0 @@
|
||||||
/+
|
|
||||||
+ d6502/base.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.base;
|
|
||||||
|
|
||||||
enum Strict : bool
|
|
||||||
{
|
|
||||||
no, yes
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Cumulative : bool
|
|
||||||
{
|
|
||||||
no, yes
|
|
||||||
}
|
|
||||||
|
|
||||||
string hexByte(int decByte)
|
|
||||||
{
|
|
||||||
int highNybble = (decByte & 0xF0) >> 4;
|
|
||||||
int lowNybble = decByte & 0x0F;
|
|
||||||
|
|
||||||
string digits = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
return digits[highNybble..(highNybble + 1)] ~
|
|
||||||
digits[lowNybble..(lowNybble + 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
final class StatusRegister
|
|
||||||
{
|
|
||||||
bool carry, decimal, interrupt, overflow;
|
|
||||||
ubyte zero_, negative_;
|
|
||||||
|
|
||||||
ubyte toByte()
|
|
||||||
{
|
|
||||||
return (carry ? 0x01 : 0) |
|
|
||||||
((zero_ == 0) ? 0x02 : 0) |
|
|
||||||
(interrupt ? 0x04 : 0) |
|
|
||||||
(decimal ? 0x08 : 0) |
|
|
||||||
0x30 | // break and reserved both set
|
|
||||||
(overflow ? 0x40 : 0) |
|
|
||||||
(negative_ & 0x80);
|
|
||||||
}
|
|
||||||
|
|
||||||
void fromByte(ubyte val)
|
|
||||||
{
|
|
||||||
carry = ((val & 0x01) != 0);
|
|
||||||
zero_ = ((val & 0x02) ? 0 : 1);
|
|
||||||
interrupt = ((val & 0x04) != 0);
|
|
||||||
decimal = ((val & 0x08) != 0);
|
|
||||||
overflow = ((val & 0x40) != 0);
|
|
||||||
negative_ = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CpuBase(bool strict, bool cumulative)
|
|
||||||
{
|
|
||||||
enum _isCpuBase = true;
|
|
||||||
static if (strict) enum _isStrict = true;
|
|
||||||
static if (cumulative) enum _isCumulative = true;
|
|
||||||
|
|
||||||
static string AbstractOpcodes()
|
|
||||||
{
|
|
||||||
string abstractOpcodes;
|
|
||||||
for (int op = 0; op < 256; ++op)
|
|
||||||
abstractOpcodes ~= "abstract void opcode" ~ hexByte(op) ~ "();\n";
|
|
||||||
return abstractOpcodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin(AbstractOpcodes());
|
|
||||||
|
|
||||||
ushort programCounter;
|
|
||||||
ubyte accumulator, xIndex, yIndex, stackPointer;
|
|
||||||
StatusRegister flag;
|
|
||||||
|
|
||||||
bool signalActive, irqActive, resetActive, nmiActive, nmiArmed;
|
|
||||||
|
|
||||||
ushort opcodePC;
|
|
||||||
ubyte opcode, operand1, operand2;
|
|
||||||
|
|
||||||
final ubyte[] save()
|
|
||||||
{
|
|
||||||
ubyte[] data = new ubyte[12];
|
|
||||||
data[0] = programCounter & 0xFF;
|
|
||||||
data[1] = programCounter >> 8;
|
|
||||||
data[2] = accumulator;
|
|
||||||
data[3] = xIndex;
|
|
||||||
data[4] = yIndex;
|
|
||||||
data[5] = stackPointer;
|
|
||||||
data[6] = flag.toByte();
|
|
||||||
data[7] = (signalActive ? 1 : 0);
|
|
||||||
data[8] = (irqActive ? 1 : 0);
|
|
||||||
data[9] = (resetActive ? 1 : 0);
|
|
||||||
data[10] = (nmiActive ? 1 : 0);
|
|
||||||
data[11] = (nmiArmed ? 1 : 0);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
final void restore(ubyte[] data)
|
|
||||||
{
|
|
||||||
assert (data.length >= 12);
|
|
||||||
|
|
||||||
programCounter = data[0] | (data[1] << 8);
|
|
||||||
accumulator = data[2];
|
|
||||||
xIndex = data[3];
|
|
||||||
yIndex = data[4];
|
|
||||||
stackPointer = data[5];
|
|
||||||
flag.fromByte(data[6]);
|
|
||||||
signalActive = ((data[7] == 0) ? false : true);
|
|
||||||
irqActive = ((data[8] == 0) ? false : true);
|
|
||||||
resetActive = ((data[9] == 0) ? false : true);
|
|
||||||
nmiActive = ((data[10] == 0) ? false : true);
|
|
||||||
nmiArmed = ((data[11] == 0) ? false : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
final void reboot()
|
|
||||||
{
|
|
||||||
restore([0, 0, 0, 0, 0, 0xFF, 0, 0, 0, 0, 0, 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
ubyte delegate(ushort addr) memoryRead;
|
|
||||||
void delegate(ushort addr, ubyte val) memoryWrite;
|
|
||||||
debug(disassemble)
|
|
||||||
{
|
|
||||||
string delegate(ushort addr) memoryName;
|
|
||||||
}
|
|
||||||
static if (cumulative) void delegate(int cycles) tick;
|
|
||||||
else void delegate() tick;
|
|
||||||
|
|
||||||
abstract void run(bool continuous);
|
|
||||||
abstract void stop();
|
|
||||||
abstract void resetLow();
|
|
||||||
abstract void nmiLow(bool signalLow);
|
|
||||||
abstract void irqLow(bool signalLow);
|
|
||||||
}
|
|
266
src/d6502/cmos.d
266
src/d6502/cmos.d
|
@ -1,266 +0,0 @@
|
||||||
/+
|
|
||||||
+ d6502/cmos.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.cmos;
|
|
||||||
|
|
||||||
import d6502.base;
|
|
||||||
import d6502.cpu;
|
|
||||||
|
|
||||||
class Cmos(bool strict, bool cumulative) : Cpu!(strict, cumulative)
|
|
||||||
{
|
|
||||||
enum _isCMOS = true;
|
|
||||||
|
|
||||||
this()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
spuriousAddress = &programCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
final override void do_IRQ_or_NMI(ushort vector)
|
|
||||||
{
|
|
||||||
super.do_IRQ_or_NMI(vector);
|
|
||||||
flag.decimal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final override void doReset()
|
|
||||||
{
|
|
||||||
super.doReset();
|
|
||||||
flag.decimal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final override void dec_addWithCarry(ubyte val)
|
|
||||||
{
|
|
||||||
super.dec_addWithCarry(val);
|
|
||||||
peek(programCounter);
|
|
||||||
// TODO: fix this
|
|
||||||
static if (cumulative) tick(1);
|
|
||||||
flag.zero_ = flag.negative_ = accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
final override void dec_subWithCarry(ubyte val)
|
|
||||||
{
|
|
||||||
int a = accumulator;
|
|
||||||
int al = (a & 0x0F) - (val & 0x0F) - !flag.carry;
|
|
||||||
a = a - val - !flag.carry;
|
|
||||||
if (a < 0)
|
|
||||||
a = a - 0x60;
|
|
||||||
if (al < 0)
|
|
||||||
a = a - 0x06;
|
|
||||||
|
|
||||||
uint diff = accumulator - val - !flag.carry;
|
|
||||||
flag.overflow =
|
|
||||||
((accumulator ^ diff) & 0x80) &&
|
|
||||||
((accumulator ^ val) & 0x80);
|
|
||||||
flag.carry = (diff < 0x100);
|
|
||||||
|
|
||||||
peek(programCounter);
|
|
||||||
// TODO: fix this
|
|
||||||
static if (cumulative) tick(1);
|
|
||||||
flag.zero_ = flag.negative_ = accumulator = cast(ubyte)a;
|
|
||||||
}
|
|
||||||
|
|
||||||
final void addrZeropageI()
|
|
||||||
{
|
|
||||||
ubyte vector = readByteOperand();
|
|
||||||
primaryAddress = readWord(vector, cast(ubyte)(vector + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
final void addrNone()
|
|
||||||
{
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ubyte testSet(ubyte val)
|
|
||||||
{
|
|
||||||
flag.zero_ = val & accumulator;
|
|
||||||
return val | accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ubyte testReset(ubyte val)
|
|
||||||
{
|
|
||||||
flag.zero_ = val & accumulator;
|
|
||||||
return val & ~accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
static string RMW(string action)
|
|
||||||
{
|
|
||||||
return "peek(primaryAddress);\n" ~
|
|
||||||
"writeFinal(primaryAddress, (flag.zero_ = flag.negative_ = " ~
|
|
||||||
action ~ "(readVal = read(primaryAddress))));\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string TestModify(string action)
|
|
||||||
{
|
|
||||||
return "peek(primaryAddress);\n" ~
|
|
||||||
"writeFinal(primaryAddress, " ~
|
|
||||||
action ~ "(readVal = read(primaryAddress)));\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string ReadNOP()
|
|
||||||
{
|
|
||||||
return "readVal = readFinal(primaryAddress);\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string ManualAddress(string name, int[] opcodes,
|
|
||||||
string mode)
|
|
||||||
{
|
|
||||||
string modes = "[[\"" ~ name ~ "\", \"NA\"], \n";
|
|
||||||
for (int op = 0; op < opcodes.length; ++op)
|
|
||||||
{
|
|
||||||
int opcode = opcodes[op];
|
|
||||||
modes ~= "[\"" ~ hexByte(opcode) ~ "\", \"" ~ mode ~ "\"]";
|
|
||||||
if (op != (opcodes.length - 1)) modes ~= ", ";
|
|
||||||
modes ~= "\n";
|
|
||||||
}
|
|
||||||
return modes ~ "]\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"ASL", "Read", [0x06, 0x0E, 0x16, 0x1E])),
|
|
||||||
RMW("shiftLeft")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"LSR", "Read", [0x46, 0x4E, 0x56, 0x5E])),
|
|
||||||
RMW("shiftRight")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"ROL", "Read", [0x26, 0x2E, 0x36, 0x3E])),
|
|
||||||
RMW("rotateLeft")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"ROR", "Read", [0x66, 0x6E, 0x76, 0x7E])),
|
|
||||||
RMW("rotateRight")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"INC", "Read", [0xE6, 0xEE, 0xF6])),
|
|
||||||
RMW("increment")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"DEC", "Read", [0xC6, 0xCE, 0xD6])),
|
|
||||||
RMW("decrement")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"INC", "Write", [0xFE])),
|
|
||||||
RMW("increment")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"DEC", "Write", [0xDE])),
|
|
||||||
RMW("decrement")));
|
|
||||||
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"BIT", "Read", [0x34, 0x3C])),
|
|
||||||
BitTest()));
|
|
||||||
mixin(Opcode([["ORA", "Read"], ["12", "ZeropageI()"]],
|
|
||||||
Read("accumulator |=")));
|
|
||||||
mixin(Opcode([["AND", "Read"], ["32", "ZeropageI()"]],
|
|
||||||
Read("accumulator &=")));
|
|
||||||
mixin(Opcode([["EOR", "Read"], ["52", "ZeropageI()"]],
|
|
||||||
Read("accumulator ^=")));
|
|
||||||
mixin(Opcode([["LDA", "Read"], ["B2", "ZeropageI()"]],
|
|
||||||
Read("accumulator =")));
|
|
||||||
mixin(Opcode([["CMP", "Read"], ["D2", "ZeropageI()"]],
|
|
||||||
Compare("accumulator")));
|
|
||||||
mixin(Opcode([["ADC", "Read"], ["72", "ZeropageI()"]],
|
|
||||||
Decimal("addWithCarry")));
|
|
||||||
mixin(Opcode([["SBC", "Read"], ["F2", "ZeropageI()"]],
|
|
||||||
Decimal("subWithCarry")));
|
|
||||||
mixin(Opcode([["STA", "Write"], ["92", "ZeropageI()"]],
|
|
||||||
Write("accumulator")));
|
|
||||||
|
|
||||||
mixin(RegisterOpcode("DEA", "3A", "accumulator -= 1"));
|
|
||||||
mixin(RegisterOpcode("INA", "1A", "accumulator += 1"));
|
|
||||||
mixin(SimpleOpcode("PHX", "DA", "push(xIndex)"));
|
|
||||||
mixin(SimpleOpcode("PHY", "5A", "push(yIndex)"));
|
|
||||||
mixin(RegisterOpcode("PLX", "FA", "xIndex = pull()"));
|
|
||||||
mixin(RegisterOpcode("PLY", "7A", "yIndex = pull()"));
|
|
||||||
mixin(BranchOpcode("BRA", "80", "true"));
|
|
||||||
|
|
||||||
mixin(Opcode([["TRB", "Read"],
|
|
||||||
["14", "Zeropage()"], ["1C", "Absolute"]],
|
|
||||||
TestModify("testReset")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"TSB", "Read", [0x04, 0x0C])),
|
|
||||||
TestModify("testSet")));
|
|
||||||
mixin(Opcode([["STZ", "Write"],
|
|
||||||
["64", "Zeropage()"], ["74", "ZeropageX()"],
|
|
||||||
["9C", "Absolute()"], ["9E", "AbsoluteX(true)"]],
|
|
||||||
Write("0")));
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x02, 0x22, 0x42, 0x62, 0x82, 0xC2, 0xE2],
|
|
||||||
"Immediate")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x44],
|
|
||||||
"Zeropage()")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x54, 0xD4, 0xF4],
|
|
||||||
"ZeropageX()")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0xDC, 0xFC],
|
|
||||||
"AbsoluteX(false)")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x03, 0x13, 0x23, 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93,
|
|
||||||
0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3, 0x07, 0x17, 0x27, 0x37,
|
|
||||||
0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xA7, 0xB7, 0xC7, 0xD7,
|
|
||||||
0xE7, 0xF7, 0x0B, 0x1B, 0x2B, 0x3B, 0x4B, 0x5B, 0x6B, 0x7B,
|
|
||||||
0x8B, 0x9B, 0xAB, 0xBB, 0xCB, 0xDB, 0xEB, 0xFB, 0x0F, 0x1F,
|
|
||||||
0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x7F, 0x8F, 0x9F, 0xAF, 0xBF,
|
|
||||||
0xCF, 0xDF, 0xEF, 0xFF], "None()")),
|
|
||||||
""));
|
|
||||||
|
|
||||||
/* NOP8 */
|
|
||||||
void opcode5C()
|
|
||||||
{
|
|
||||||
readByteOperand();
|
|
||||||
peek(programCounter++);
|
|
||||||
peek(0xFF00 | operand1);
|
|
||||||
peek(0xFFFF);
|
|
||||||
peek(0xFFFF);
|
|
||||||
peek(0xFFFF);
|
|
||||||
peek(0xFFFF);
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JMP ($$$$) */
|
|
||||||
override void opcode6C()
|
|
||||||
{
|
|
||||||
ushort vector = readWordOperand();
|
|
||||||
peek(programCounter);
|
|
||||||
programCounter = readWord(vector, cast(ushort)(vector + 1));
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JMP ($$$$,X) */
|
|
||||||
void opcode7C()
|
|
||||||
{
|
|
||||||
baseAddress = readWordOperand();
|
|
||||||
peek(programCounter);
|
|
||||||
ushort vector = cast(ushort)(baseAddress + xIndex);
|
|
||||||
programCounter = readWord(vector, cast(ushort)(vector + 1));
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BIT #$$ */
|
|
||||||
void opcode89()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter++);
|
|
||||||
flag.zero_ = accumulator & readVal;
|
|
||||||
}
|
|
||||||
}
|
|
751
src/d6502/cpu.d
751
src/d6502/cpu.d
|
@ -1,751 +0,0 @@
|
||||||
/+
|
|
||||||
+ 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/+
|
|
||||||
+ d6502/nmosbase.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.nmosbase;
|
|
||||||
|
|
||||||
import d6502.cpu;
|
|
||||||
|
|
||||||
class NmosBase(bool strict, bool cumulative) : Cpu!(strict, cumulative)
|
|
||||||
{
|
|
||||||
enum _isNMOS = true;
|
|
||||||
|
|
||||||
this()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
spuriousAddress = &badAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
static string RMW(string action)
|
|
||||||
{
|
|
||||||
return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~
|
|
||||||
"writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~
|
|
||||||
action ~ "(readVal));\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"ASL", "Write", [0x06, 0x0E, 0x16, 0x1E])),
|
|
||||||
RMW("shiftLeft")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"LSR", "Write", [0x46, 0x4E, 0x56, 0x5E])),
|
|
||||||
RMW("shiftRight")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"ROL", "Write", [0x26, 0x2E, 0x36, 0x3E])),
|
|
||||||
RMW("rotateLeft")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"ROR", "Write", [0x66, 0x6E, 0x76, 0x7E])),
|
|
||||||
RMW("rotateRight")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"INC", "Write", [0xE6, 0xEE, 0xF6, 0xFE])),
|
|
||||||
RMW("increment")));
|
|
||||||
mixin(Opcode(mixin(Type2Address(
|
|
||||||
"DEC", "Write", [0xC6, 0xCE, 0xD6, 0xDE])),
|
|
||||||
RMW("decrement")));
|
|
||||||
|
|
||||||
/* JMP ($$$$) */
|
|
||||||
override void opcode6C()
|
|
||||||
{
|
|
||||||
ushort vector = readWordOperand();
|
|
||||||
programCounter = readWord(vector,
|
|
||||||
(vector & 0xFF00) | cast(ubyte)(vector + 1));
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,365 +0,0 @@
|
||||||
/+
|
|
||||||
+ d6502/nmosundoc.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.nmosundoc;
|
|
||||||
|
|
||||||
import d6502.base;
|
|
||||||
import d6502.nmosbase;
|
|
||||||
|
|
||||||
class NmosUndoc(bool strict, bool cumulative) : NmosBase!(strict, cumulative)
|
|
||||||
{
|
|
||||||
this()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
final void addrHalt()
|
|
||||||
{
|
|
||||||
programCounter--;
|
|
||||||
/* TODO: check with the timer how many ticks until it would
|
|
||||||
* stop me? */
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
final void addrImplied()
|
|
||||||
{
|
|
||||||
peek(programCounter);
|
|
||||||
static if (cumulative) tick(totalCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
final void strange(ubyte val)
|
|
||||||
{
|
|
||||||
version(Commodore64)
|
|
||||||
{
|
|
||||||
ubyte hiAddr = cast(ubyte)((primaryAddress >> 8) + 1);
|
|
||||||
val = val & hiAddr;
|
|
||||||
ushort addr = (badAddress == primaryAddress) ? primaryAddress :
|
|
||||||
((val << 8) | (primaryAddress & 0xFF));
|
|
||||||
writeFinal(addr, val);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ubyte hiAddr = cast(ubyte)((baseAddress >> 8) + 1);
|
|
||||||
writeFinal(primaryAddress, val & hiAddr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static string UndocAddress(string name, string rw, int[] opcodes)
|
|
||||||
{
|
|
||||||
string type = (rw == "Write") ? "true" : "false";
|
|
||||||
string modes = "[[\"" ~ name ~ "\", \"" ~ rw ~ "\"], \n";
|
|
||||||
string index = (name[2] == 'X') ? "Y" : "X";
|
|
||||||
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 3:
|
|
||||||
modes ~= "Absolute()";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
modes ~= "IndirectY("~ type ~ ")";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
modes ~= "Zeropage" ~ index ~ "()";
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
modes ~= "AbsoluteY(" ~ type ~ ")";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
modes ~= "\"]";
|
|
||||||
if (op != (opcodes.length - 1)) modes ~= ", ";
|
|
||||||
modes ~= "\n";
|
|
||||||
}
|
|
||||||
return modes ~ "]\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string ManualAddress(string name, int[] opcodes,
|
|
||||||
string mode)
|
|
||||||
{
|
|
||||||
string modes = "[[\"" ~ name ~ "\", \"NA\"], \n";
|
|
||||||
for (int op = 0; op < opcodes.length; ++op)
|
|
||||||
{
|
|
||||||
int opcode = opcodes[op];
|
|
||||||
modes ~= "[\"" ~ hexByte(opcode) ~ "\", \"" ~ mode ~ "\"]";
|
|
||||||
if (op != (opcodes.length - 1)) modes ~= ", ";
|
|
||||||
modes ~= "\n";
|
|
||||||
}
|
|
||||||
return modes ~ "]\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string Halt()
|
|
||||||
{
|
|
||||||
/+ TODO: have this do something useful +/
|
|
||||||
return "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string ReadNOP()
|
|
||||||
{
|
|
||||||
return "readVal = readFinal(primaryAddress);\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string RMW_Read(string action1, string action2)
|
|
||||||
{
|
|
||||||
return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~
|
|
||||||
"writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~
|
|
||||||
"(writeVal = " ~ action1 ~ "(readVal)));\n" ~
|
|
||||||
"flag.zero_ = flag.negative_ = (" ~action2 ~ " writeVal);\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string RMW_Compare(string action1, string action2)
|
|
||||||
{
|
|
||||||
return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~
|
|
||||||
"writeFinal(primaryAddress, " ~
|
|
||||||
"(writeVal = " ~ action1 ~ "(readVal)));\n" ~
|
|
||||||
"flag.zero_ = flag.negative_ = " ~
|
|
||||||
"compare(" ~ action2 ~ ", writeVal);\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static string RMW_Decimal(string action1, string action2)
|
|
||||||
{
|
|
||||||
return "poke(primaryAddress, (readVal = read(primaryAddress)));\n" ~
|
|
||||||
"writeFinal(primaryAddress, flag.zero_ = flag.negative_ = " ~
|
|
||||||
"(writeVal = " ~ action1 ~ "(readVal)));\n" ~
|
|
||||||
"if (flag.decimal) dec_" ~ action2 ~ "(writeVal);\n" ~
|
|
||||||
"else hex_" ~ action2 ~ "(writeVal);\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"HLT", [0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, 0x92, 0xB2,
|
|
||||||
0xD2, 0xF2], "Halt()")),
|
|
||||||
Halt()));
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x1A, 0x3A, 0x5A, 0x7A, 0xDA, 0xFA], "Implied()")),
|
|
||||||
""));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x0C],
|
|
||||||
"Absolute()")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x1C, 0x3C, 0x5C, 0x7C, 0xDC, 0xFC],
|
|
||||||
"AbsoluteX(false)")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x80, 0x82, 0x89, 0xC2, 0xE2],
|
|
||||||
"Immediate")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x04, 0x44, 0x64],
|
|
||||||
"Zeropage()")),
|
|
||||||
ReadNOP()));
|
|
||||||
mixin(Opcode(mixin(ManualAddress(
|
|
||||||
"NOP", [0x14, 0x34, 0x54, 0x74, 0xD4, 0xF4],
|
|
||||||
"ZeropageX()")),
|
|
||||||
ReadNOP()));
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(UndocAddress(
|
|
||||||
"LAX", "Read", [0xA3, 0xA7, 0xAF, 0xB3, 0xB7, 0xBF])),
|
|
||||||
Read("accumulator = xIndex =")));
|
|
||||||
mixin(Opcode(mixin(UndocAddress(
|
|
||||||
"SAX", "Write", [0x83, 0x87, 0x97, 0x8F])),
|
|
||||||
Write("accumulator & xIndex")));
|
|
||||||
|
|
||||||
mixin(Opcode(mixin(Type1Address(
|
|
||||||
"ASO", "Write", [0x03, 0x07, 0x0F, 0x13, 0x17, 0x1B, 0x1F])),
|
|
||||||
RMW_Read("shiftLeft", "accumulator |=")));
|
|
||||||
mixin(Opcode(mixin(Type1Address(
|
|
||||||
"RLA", "Write", [0x23, 0x27, 0x2F, 0x33, 0x37, 0x3B, 0x3F])),
|
|
||||||
RMW_Read("rotateLeft", "accumulator &=")));
|
|
||||||
mixin(Opcode(mixin(Type1Address(
|
|
||||||
"LSE", "Write", [0x43, 0x47, 0x4F, 0x53, 0x57, 0x5B, 0x5F])),
|
|
||||||
RMW_Read("shiftRight", "accumulator ^=")));
|
|
||||||
mixin(Opcode(mixin(Type1Address(
|
|
||||||
"DCM", "Write", [0xC3, 0xC7, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF])),
|
|
||||||
RMW_Compare("decrement", "accumulator")));
|
|
||||||
mixin(Opcode(mixin(Type1Address(
|
|
||||||
"RRA", "Write", [0x63, 0x67, 0x6F, 0x73, 0x77, 0x7B, 0x7F])),
|
|
||||||
RMW_Decimal("rotateRight", "addWithCarry")));
|
|
||||||
mixin(Opcode(mixin(Type1Address(
|
|
||||||
"INS", "Write", [0xE3, 0xE7, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF])),
|
|
||||||
RMW_Decimal("increment", "subWithCarry")));
|
|
||||||
|
|
||||||
/* ANC #$$ */
|
|
||||||
override void opcode0B()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter);
|
|
||||||
flag.zero_ = flag.negative_ = (accumulator = readVal);
|
|
||||||
flag.carry = (flag.negative_ > 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ANC #$$ */
|
|
||||||
override void opcode2B()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter);
|
|
||||||
flag.zero_ = flag.negative_ = (accumulator = readVal);
|
|
||||||
flag.carry = (flag.negative_ > 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ALR #$$ */
|
|
||||||
override void opcode4B()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter);
|
|
||||||
flag.zero_ = flag.negative_ =
|
|
||||||
(accumulator = shiftRight(accumulator & readVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ARR #$$ */
|
|
||||||
override void opcode6B()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter);
|
|
||||||
ubyte val = readVal & accumulator;
|
|
||||||
if (flag.decimal) {
|
|
||||||
ubyte temp = cast(ubyte)((val >> 1) + (flag.carry ? 0x80 : 0));
|
|
||||||
flag.zero_ = flag.negative_ = temp;
|
|
||||||
flag.overflow = (((temp ^ val) & 0x40) != 0);
|
|
||||||
if ((readVal & 0x0F) + (val & 0x01) > 5)
|
|
||||||
temp = (temp & 0xF0) + ((temp + 0x6) & 0x0F);
|
|
||||||
if (val + (val & 0x10) >= 0x60)
|
|
||||||
{
|
|
||||||
temp += 0x60;
|
|
||||||
flag.carry = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
flag.carry = false;
|
|
||||||
accumulator = temp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
accumulator = cast(ubyte)((val >> 1) + (flag.carry ? 0x80 : 0));
|
|
||||||
flag.zero_ = flag.negative_ = accumulator;
|
|
||||||
val >>= 7;
|
|
||||||
flag.carry = (val != 0);
|
|
||||||
flag.overflow = ((val ^ ((accumulator >> 5) & 1)) != 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ANE #$$ */
|
|
||||||
override void opcode8B()
|
|
||||||
{
|
|
||||||
// unstable
|
|
||||||
readVal = operand1 = readFinal(programCounter++);
|
|
||||||
|
|
||||||
version(Atari8Bit)
|
|
||||||
{
|
|
||||||
flag.zero_ = flag.negative_ =
|
|
||||||
(accumulator & xIndex & readVal);
|
|
||||||
accumulator &= xIndex & (operand1 | 0xEF);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
flag.zero_ = flag.negative_ =
|
|
||||||
(accumulator &= (xIndex & readVal));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SHA ($$),Y */
|
|
||||||
void opcode93()
|
|
||||||
{
|
|
||||||
addrIndirectY(true);
|
|
||||||
strange(accumulator & xIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SHA $$$$,Y */
|
|
||||||
void opcode9F()
|
|
||||||
{
|
|
||||||
addrAbsoluteY(true);
|
|
||||||
strange(accumulator & xIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SHS $$$$,Y */
|
|
||||||
void opcode9B()
|
|
||||||
{
|
|
||||||
addrAbsoluteY(true);
|
|
||||||
strange(stackPointer = (accumulator & xIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SHY $$$$,X */
|
|
||||||
void opcode9C()
|
|
||||||
{
|
|
||||||
addrAbsoluteX(true);
|
|
||||||
strange(yIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SHX $$$$,Y */
|
|
||||||
void opcode9E()
|
|
||||||
{
|
|
||||||
addrAbsoluteY(true);
|
|
||||||
strange(xIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LAX #$$ */
|
|
||||||
override void opcodeAB()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter);
|
|
||||||
|
|
||||||
version(Commodore128)
|
|
||||||
{
|
|
||||||
// not unstable?
|
|
||||||
flag.zero_ = flag.negative_ =
|
|
||||||
(accumulator = readVal);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// unstable
|
|
||||||
version(Commodore64)
|
|
||||||
{
|
|
||||||
accumulator |= 0xEE;
|
|
||||||
}
|
|
||||||
flag.zero_ = flag.negative_ =
|
|
||||||
(accumulator &= readVal);
|
|
||||||
}
|
|
||||||
xIndex = accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LAS $$$$,Y */
|
|
||||||
override void opcodeBB()
|
|
||||||
{
|
|
||||||
addrAbsoluteY(false);
|
|
||||||
readVal = readFinal(primaryAddress);
|
|
||||||
|
|
||||||
flag.zero_ = flag.negative_ =
|
|
||||||
(xIndex = accumulator = (stackPointer & readVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SBX #$$ */
|
|
||||||
override void opcodeCB()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter++);
|
|
||||||
xIndex &= accumulator;
|
|
||||||
flag.zero_ = flag.negative_ = compare(xIndex, readVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SBC #$$ */
|
|
||||||
override void opcodeEB()
|
|
||||||
{
|
|
||||||
readVal = operand1 = readFinal(programCounter++);
|
|
||||||
if (flag.decimal) dec_subWithCarry(readVal);
|
|
||||||
else hex_subWithCarry(readVal);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,9 +24,7 @@ module system.base;
|
||||||
|
|
||||||
import timer;
|
import timer;
|
||||||
import memory;
|
import memory;
|
||||||
import d6502.base;
|
import cpu.d6502;
|
||||||
|
|
||||||
private alias d6502.base.CpuBase!(Strict.no, Cumulative.no) CpuBase;
|
|
||||||
|
|
||||||
import ui.sound;
|
import ui.sound;
|
||||||
import ui.inputevents;
|
import ui.inputevents;
|
||||||
|
@ -36,14 +34,31 @@ import system.video;
|
||||||
import iomem;
|
import iomem;
|
||||||
import video.base;
|
import video.base;
|
||||||
import system.peripheral;
|
import system.peripheral;
|
||||||
|
import ioummu;
|
||||||
|
|
||||||
class System
|
|
||||||
|
class SystemBase
|
||||||
|
{
|
||||||
|
Video video_;
|
||||||
|
|
||||||
|
abstract void reboot();
|
||||||
|
abstract void reset();
|
||||||
|
abstract uint checkpoint();
|
||||||
|
abstract uint sinceCheckpoint(uint cp);
|
||||||
|
abstract void execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
class System(string chip) : SystemBase
|
||||||
{
|
{
|
||||||
Timer timer;
|
Timer timer;
|
||||||
Timer.Cycle deviceCycle;
|
Timer.Cycle deviceCycle;
|
||||||
AddressDecoder decoder;
|
AddressDecoder decoder;
|
||||||
SoftSwitchPage switches;
|
SoftSwitchPage switches;
|
||||||
CpuBase cpu;
|
|
||||||
|
Cpu!(chip, AddressDecoder, Timer) cpu;
|
||||||
|
// XXX
|
||||||
|
bool* cpuRun, signalActive, resetLow;
|
||||||
|
|
||||||
IOMem ioMem;
|
IOMem ioMem;
|
||||||
Peripherals peripherals;
|
Peripherals peripherals;
|
||||||
|
|
||||||
|
@ -73,15 +88,15 @@ class System
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Video video_;
|
|
||||||
Memory memory_;
|
Memory memory_;
|
||||||
IO io_;
|
IO io_;
|
||||||
|
|
||||||
abstract IO newIO();
|
static if (chip == "65C02")
|
||||||
abstract CpuBase newCpu();
|
{
|
||||||
abstract Video newVideo(ubyte[] vidRom);
|
AuxiliaryCard auxCard;
|
||||||
abstract IOMem newIOMem();
|
MMU mmu;
|
||||||
abstract Peripherals newPeripherals();
|
IOU iou;
|
||||||
|
}
|
||||||
|
|
||||||
this(ubyte[] romDump)
|
this(ubyte[] romDump)
|
||||||
{
|
{
|
||||||
|
@ -91,23 +106,33 @@ class System
|
||||||
initIO(null); // XXX where is vidRom passed in?
|
initIO(null); // XXX where is vidRom passed in?
|
||||||
decoder.nullRead = &video_.scanner.floatingBus;
|
decoder.nullRead = &video_.scanner.floatingBus;
|
||||||
|
|
||||||
peripherals = newPeripherals();
|
static if (chip == "6502")
|
||||||
peripherals.install(cpu, decoder, memory_.mainRom);
|
peripherals = new Peripherals_II();
|
||||||
|
else
|
||||||
|
peripherals = new Peripherals_IIe();
|
||||||
|
peripherals.install(decoder, memory_.mainRom);
|
||||||
ioMem.initialize(decoder, switches, timer, peripherals);
|
ioMem.initialize(decoder, switches, timer, peripherals);
|
||||||
|
|
||||||
input.onReset = &reset;
|
input.onReset = &reset;
|
||||||
switches.setFloatingBus(&video_.scanner.floatingBus);
|
switches.setFloatingBus(&video_.scanner.floatingBus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reboot()
|
override void reboot()
|
||||||
{
|
{
|
||||||
cpu.reboot();
|
// XXX replace
|
||||||
|
//cpu.reboot();
|
||||||
deviceCycle.restart();
|
deviceCycle.restart();
|
||||||
memory_.reboot();
|
memory_.reboot();
|
||||||
ioMem.reboot();
|
ioMem.reboot();
|
||||||
io_.reboot();
|
io_.reboot();
|
||||||
peripherals.reboot();
|
peripherals.reboot();
|
||||||
video_.reboot();
|
video_.reboot();
|
||||||
|
|
||||||
|
static if (chip == "65C02")
|
||||||
|
{
|
||||||
|
auxCard.reboot();
|
||||||
|
mmu.reboot();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initTimer()
|
void initTimer()
|
||||||
|
@ -120,56 +145,92 @@ class System
|
||||||
|
|
||||||
void initMemory(ubyte[] romDump)
|
void initMemory(ubyte[] romDump)
|
||||||
{
|
{
|
||||||
|
static if (chip == "65C02")
|
||||||
|
{
|
||||||
|
mmu = new MMU();
|
||||||
|
mmu.ioMem = new IOMem_IIe();
|
||||||
|
mmu.ioMem.setRom(romDump);
|
||||||
|
}
|
||||||
memory_ = new Memory(romDump);
|
memory_ = new Memory(romDump);
|
||||||
decoder = new AddressDecoder();
|
decoder = new AddressDecoder();
|
||||||
switches = new SoftSwitchPage();
|
switches = new SoftSwitchPage();
|
||||||
decoder.installSwitches(switches);
|
decoder.installSwitches(switches);
|
||||||
decoder.install(memory_.mainRam);
|
decoder.install(memory_.mainRam);
|
||||||
decoder.install(memory_.mainRom);
|
decoder.install(memory_.mainRom);
|
||||||
ioMem = newIOMem();
|
static if (chip == "6502")
|
||||||
|
ioMem = new IOMem();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ioMem = mmu.ioMem;
|
||||||
|
auxCard = new Extended80ColumnCard();
|
||||||
|
mmu.init(switches, auxCard, decoder, memory_.mainRam,
|
||||||
|
memory_.mainRom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initCpu()
|
void initCpu()
|
||||||
{
|
{
|
||||||
cpu = newCpu();
|
cpu = new Cpu!(chip, AddressDecoder, Timer)(decoder, timer);
|
||||||
|
// XXX
|
||||||
|
cpuRun = &cpu.keepRunning;
|
||||||
|
signalActive = &cpu.signalActive;
|
||||||
|
resetLow = &cpu.resetLow;
|
||||||
|
|
||||||
debug(disassemble) cpu.memoryName = &decoder.memoryReadName;
|
debug(disassemble) cpu.memoryName = &decoder.memoryReadName;
|
||||||
cpu.tick = &timer.tick;
|
|
||||||
timer.onPrimaryStop(&primaryStop);
|
timer.onPrimaryStop(&primaryStop);
|
||||||
cpu.memoryRead = &decoder.read;
|
|
||||||
cpu.memoryWrite = &decoder.write;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initIO(ubyte[] vidRom)
|
void initIO(ubyte[] vidRom)
|
||||||
{
|
{
|
||||||
io_ = newIO();
|
static if (chip == "6502")
|
||||||
video_ = newVideo(vidRom);
|
{
|
||||||
|
io_ = new IO_II(switches, timer, deviceCycle);
|
||||||
|
video_ = new Video_II(switches, memory_.vidPages, timer, vidRom,
|
||||||
|
&io_.kbd.peekLatch, decoder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io_ = new IO_IIe(switches, timer, deviceCycle);
|
||||||
|
video_ = new Video_IIe(switches, memory_.vidPages, timer, vidRom,
|
||||||
|
&io_.kbd.peekLatch, auxCard.vidPages);
|
||||||
|
iou = new IOU(io_, video_.signal);
|
||||||
|
iou.initSwitches(switches);
|
||||||
|
mmu.initIO(video_.scanner, &io_.kbd.peekLatch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool primaryStop()
|
bool primaryStop()
|
||||||
{
|
{
|
||||||
cpu.stop();
|
*cpuRun = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset()
|
override void reset()
|
||||||
{
|
{
|
||||||
|
static if (chip == "65C02")
|
||||||
|
{
|
||||||
|
auxCard.reset();
|
||||||
|
mmu.reset();
|
||||||
|
}
|
||||||
|
|
||||||
peripherals.reset();
|
peripherals.reset();
|
||||||
cpu.resetLow();
|
*signalActive = true;
|
||||||
|
*resetLow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint checkpoint()
|
override uint checkpoint()
|
||||||
{
|
{
|
||||||
return timer.primaryCounter.currentLength;
|
return timer.primaryCounter.currentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint sinceCheckpoint(uint cp)
|
override uint sinceCheckpoint(uint cp)
|
||||||
{
|
{
|
||||||
uint currentLength = timer.primaryCounter.currentLength;
|
uint currentLength = timer.primaryCounter.currentLength;
|
||||||
return ((currentLength == timer.primaryCounter.startLength) ?
|
return ((currentLength == timer.primaryCounter.startLength) ?
|
||||||
cp : (cp - currentLength));
|
cp : (cp - currentLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
void execute()
|
override void execute()
|
||||||
{
|
{
|
||||||
cpu.run(true);
|
cpu.run(true);
|
||||||
|
|
||||||
|
@ -181,126 +242,3 @@ class System
|
||||||
// XXX peripherals get notification
|
// XXX peripherals get notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class II : System
|
|
||||||
{
|
|
||||||
import d6502.nmosundoc : NmosUndoc;
|
|
||||||
|
|
||||||
int revision;
|
|
||||||
|
|
||||||
this(ubyte[] romDump)
|
|
||||||
{
|
|
||||||
// XXX FIXME XXX
|
|
||||||
revision = int.max;
|
|
||||||
super(romDump);
|
|
||||||
}
|
|
||||||
|
|
||||||
CpuBase newCpu()
|
|
||||||
{
|
|
||||||
return new NmosUndoc!(Strict.no, Cumulative.no)();
|
|
||||||
}
|
|
||||||
|
|
||||||
IO newIO()
|
|
||||||
{
|
|
||||||
return new IO_II(switches, timer, deviceCycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
Video newVideo(ubyte[] vidRom)
|
|
||||||
{
|
|
||||||
return new Video_II(switches, memory_.vidPages, timer, vidRom,
|
|
||||||
&io_.kbd.peekLatch, decoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
IOMem newIOMem()
|
|
||||||
{
|
|
||||||
return new IOMem();
|
|
||||||
}
|
|
||||||
|
|
||||||
Peripherals newPeripherals()
|
|
||||||
{
|
|
||||||
return new Peripherals_II();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import ioummu;
|
|
||||||
|
|
||||||
class IIe : System
|
|
||||||
{
|
|
||||||
import d6502.cmos : Cmos;
|
|
||||||
|
|
||||||
AuxiliaryCard auxCard;
|
|
||||||
MMU mmu;
|
|
||||||
IOU iou;
|
|
||||||
|
|
||||||
this(ubyte[] romDump)
|
|
||||||
{
|
|
||||||
// XXX if different or no aux card?
|
|
||||||
//auxMemory = new Memory();
|
|
||||||
super(romDump);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reboot()
|
|
||||||
{
|
|
||||||
super.reboot();
|
|
||||||
auxCard.reboot();
|
|
||||||
mmu.reboot();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
auxCard.reset();
|
|
||||||
mmu.reset();
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
CpuBase newCpu()
|
|
||||||
{
|
|
||||||
// XXX this is enhanced
|
|
||||||
return new Cmos!(Strict.no, Cumulative.no)();
|
|
||||||
}
|
|
||||||
|
|
||||||
IO newIO()
|
|
||||||
{
|
|
||||||
return new IO_IIe(switches, timer, deviceCycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
Video newVideo(ubyte[] vidRom)
|
|
||||||
{
|
|
||||||
return new Video_IIe(switches, memory_.vidPages, timer, vidRom,
|
|
||||||
&io_.kbd.peekLatch, auxCard.vidPages);
|
|
||||||
}
|
|
||||||
|
|
||||||
IOMem newIOMem()
|
|
||||||
{
|
|
||||||
return mmu.ioMem;
|
|
||||||
}
|
|
||||||
|
|
||||||
Peripherals newPeripherals()
|
|
||||||
{
|
|
||||||
return new Peripherals_IIe();
|
|
||||||
}
|
|
||||||
|
|
||||||
void initMemory(ubyte[] romDump)
|
|
||||||
{
|
|
||||||
mmu = new MMU();
|
|
||||||
mmu.ioMem = new IOMem_IIe();
|
|
||||||
mmu.ioMem.setRom(romDump);
|
|
||||||
|
|
||||||
super.initMemory(romDump);
|
|
||||||
|
|
||||||
// XXX XXX XXX
|
|
||||||
// allow for different card from config
|
|
||||||
auxCard = new Extended80ColumnCard();
|
|
||||||
|
|
||||||
mmu.init(switches, auxCard, decoder, memory_.mainRam,
|
|
||||||
memory_.mainRom);
|
|
||||||
}
|
|
||||||
|
|
||||||
void initIO(ubyte[] vidRom)
|
|
||||||
{
|
|
||||||
super.initIO(vidRom);
|
|
||||||
iou = new IOU(io_, video_.signal);
|
|
||||||
iou.initSwitches(switches);
|
|
||||||
mmu.initIO(video_.scanner, &io_.kbd.peekLatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,9 +23,6 @@
|
||||||
module system.peripheral;
|
module system.peripheral;
|
||||||
|
|
||||||
import memory;
|
import memory;
|
||||||
import d6502.base;
|
|
||||||
|
|
||||||
private alias d6502.base.CpuBase!(Strict.no, Cumulative.no) CpuBase;
|
|
||||||
|
|
||||||
import peripheral.base;
|
import peripheral.base;
|
||||||
import peripheral.diskii;
|
import peripheral.diskii;
|
||||||
|
@ -36,7 +33,7 @@ class Peripherals
|
||||||
{
|
{
|
||||||
Peripheral[8] cards;
|
Peripheral[8] cards;
|
||||||
|
|
||||||
abstract void install(CpuBase cpu, AddressDecoder decoder, Rom mainRom);
|
abstract void install(AddressDecoder decoder, Rom mainRom);
|
||||||
|
|
||||||
void reboot()
|
void reboot()
|
||||||
{
|
{
|
||||||
|
@ -59,7 +56,7 @@ class Peripherals
|
||||||
|
|
||||||
class Peripherals_II : Peripherals
|
class Peripherals_II : Peripherals
|
||||||
{
|
{
|
||||||
void install(CpuBase cpu, AddressDecoder decoder, Rom mainRom)
|
void install(AddressDecoder decoder, Rom mainRom)
|
||||||
{
|
{
|
||||||
auto diskController = new Controller();
|
auto diskController = new Controller();
|
||||||
cards[6] = diskController; // XXX
|
cards[6] = diskController; // XXX
|
||||||
|
@ -78,7 +75,7 @@ class Peripherals_II : Peripherals
|
||||||
|
|
||||||
class Peripherals_IIe : Peripherals
|
class Peripherals_IIe : Peripherals
|
||||||
{
|
{
|
||||||
void install(CpuBase cpu, AddressDecoder decoder, Rom mainRom)
|
void install(AddressDecoder decoder, Rom mainRom)
|
||||||
{
|
{
|
||||||
auto diskController = new Controller();
|
auto diskController = new Controller();
|
||||||
cards[6] = diskController; // XXX
|
cards[6] = diskController; // XXX
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
import d6502.base;
|
import cpu.d6502;
|
||||||
import timer;
|
import timer;
|
||||||
import memory;
|
import memory;
|
||||||
import system.base;
|
import system.base;
|
||||||
|
@ -39,20 +39,6 @@ import peripheral.diskii;
|
||||||
import peripheral.langcard;
|
import peripheral.langcard;
|
||||||
import peripheral.saturn128;
|
import peripheral.saturn128;
|
||||||
|
|
||||||
class TestSystem : II
|
|
||||||
{
|
|
||||||
this(ubyte[] romDump)
|
|
||||||
{
|
|
||||||
super(romDump);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRom(ubyte[] rom_data)
|
|
||||||
{
|
|
||||||
uint rom_len = cast(uint)rom_data.length;
|
|
||||||
memory_.mainRom.data_[0..12288] = rom_data[(rom_len - 12288)..rom_len];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import std.file;
|
import std.file;
|
||||||
import std.string;
|
import std.string;
|
||||||
import device.speaker;
|
import device.speaker;
|
||||||
|
@ -86,11 +72,11 @@ void main(string[] args)
|
||||||
TwoappleFile romFile = TwoappleFilePicker.open("ROM file", &checkRomFile);
|
TwoappleFile romFile = TwoappleFilePicker.open("ROM file", &checkRomFile);
|
||||||
if (romFile is null) return;
|
if (romFile is null) return;
|
||||||
|
|
||||||
System sys;
|
SystemBase sys;
|
||||||
if ((args.length > 1) && (args[1] == "--iie"))
|
if ((args.length > 1) && (args[1] == "--iie"))
|
||||||
sys = new IIe(cast(ubyte[])std.file.read(romFile.fileName));
|
sys = new System!"65C02"(cast(ubyte[])std.file.read(romFile.fileName));
|
||||||
else
|
else
|
||||||
sys = new II(cast(ubyte[])std.file.read(romFile.fileName));
|
sys = new System!"6502"(cast(ubyte[])std.file.read(romFile.fileName));
|
||||||
appWindow.initSystem(sys);
|
appWindow.initSystem(sys);
|
||||||
// XXX hack
|
// XXX hack
|
||||||
appWindow.configChanged = true;
|
appWindow.configChanged = true;
|
||||||
|
|
|
@ -54,7 +54,7 @@ class TwoappleMainWindow : MainWindow
|
||||||
import gtk.Alignment;
|
import gtk.Alignment;
|
||||||
import peripheral.base;
|
import peripheral.base;
|
||||||
|
|
||||||
System system;
|
SystemBase system;
|
||||||
Label speedLabel;
|
Label speedLabel;
|
||||||
ToolItem speedItem;
|
ToolItem speedItem;
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ class TwoappleMainWindow : MainWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initSystem(System sys)
|
void initSystem(SystemBase sys)
|
||||||
{
|
{
|
||||||
showAll();
|
showAll();
|
||||||
system = sys;
|
system = sys;
|
||||||
|
|
330
test/base.d
330
test/base.d
|
@ -1,10 +1,130 @@
|
||||||
module test.base;
|
module test.base;
|
||||||
|
|
||||||
|
|
||||||
import std.algorithm, std.array, std.conv, std.exception, std.stdio,
|
import std.algorithm, std.array, std.conv, std.exception, std.getopt,
|
||||||
std.string;
|
std.stdio, std.string;
|
||||||
|
|
||||||
import test.cpu, test.opcodes;
|
import test.cpu, test.opcodes;
|
||||||
|
import cpu.data_d6502;
|
||||||
|
|
||||||
|
|
||||||
|
version(Strict)
|
||||||
|
enum strict = true;
|
||||||
|
else
|
||||||
|
enum strict = false;
|
||||||
|
version(Cumulative)
|
||||||
|
enum cumulative = true;
|
||||||
|
else
|
||||||
|
enum cumulative = false;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A test is a combination of setups, an expectation, a runner, and a
|
||||||
|
* reporter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A setup function for a given opcode puts cpu, data, info, and msg
|
||||||
|
* into the appropriate state and then calls the next function (see
|
||||||
|
* testCallNext) with the modified values. To setup multiple
|
||||||
|
* scenarios, just call the next function multiple times.
|
||||||
|
*
|
||||||
|
* Values for cpu registers are set up using the setXXX(cpu, val)
|
||||||
|
* functions; see testCallNext and OpInfo for descriptions of other
|
||||||
|
* types of information that may need to be set up.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* // prepare the accumulator with a value different from expected
|
||||||
|
* setA(cpu, ~0x10);
|
||||||
|
* // prepare memory with LDA #$10 at address $1000
|
||||||
|
* setPC(cpu, 0x1000);
|
||||||
|
* callNext("LDA immediate, positive", [Block(0x1000, [0xA9, 0x10])]);
|
||||||
|
*/
|
||||||
|
alias void delegate(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||||
|
string msg, TestSetup* next)
|
||||||
|
testsetup;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A mixin that simplifies calling the next setup function.
|
||||||
|
*
|
||||||
|
* newMsg will be appended to the current msg and passed to the next
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* To place values in memory, pass an array of Blocks as the second
|
||||||
|
* parameter. It will be appended to the current data.
|
||||||
|
*/
|
||||||
|
template testCallNext()
|
||||||
|
{
|
||||||
|
void callNext(string newMsg = "", Block[] newData = [])
|
||||||
|
{
|
||||||
|
if (*next !is null)
|
||||||
|
next.run(opcode, cpu, data ~ newData, info, msg ~ newMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A block of memory with a given base address.
|
||||||
|
*
|
||||||
|
* For example, `Block(0x1000, [0xA9, 0x10])`
|
||||||
|
*/
|
||||||
|
struct Block
|
||||||
|
{
|
||||||
|
const ushort base;
|
||||||
|
const(ubyte[]) data;
|
||||||
|
|
||||||
|
string toString() const
|
||||||
|
{
|
||||||
|
return format("Block(%0.4X, [%s])", base, formatMemory(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Information about expected opcode execution.
|
||||||
|
struct OpInfo
|
||||||
|
{
|
||||||
|
// The effective address, if any.
|
||||||
|
ushort addr;
|
||||||
|
// The data to be read or written, if any.
|
||||||
|
ubyte data;
|
||||||
|
// The length of the opcode + operands.
|
||||||
|
int len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestSetup
|
||||||
|
{
|
||||||
|
testsetup setup;
|
||||||
|
TestSetup next;
|
||||||
|
|
||||||
|
auto static opCall(testsetup d)
|
||||||
|
{
|
||||||
|
auto obj = new TestSetup();
|
||||||
|
obj.setup = d;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(ubyte opcode, CpuInfo cpu = CpuInfo(), Block[] data = [],
|
||||||
|
OpInfo info = OpInfo(), string msg = "")
|
||||||
|
{
|
||||||
|
setup(opcode, cpu, data, info, msg, &next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TestSetup connect(TestSetup first, TestSetup[] rest...)
|
||||||
|
{
|
||||||
|
if (!(rest.empty))
|
||||||
|
{
|
||||||
|
auto x = first;
|
||||||
|
while (x.next !is null) x = x.next;
|
||||||
|
x.next = connect(rest[0], rest[1..$]);
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -104,19 +224,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A block of memory with a given base address.
|
|
||||||
struct Block
|
|
||||||
{
|
|
||||||
const ushort base;
|
|
||||||
const(ubyte[]) data;
|
|
||||||
|
|
||||||
string toString() const
|
|
||||||
{
|
|
||||||
return format("Block(%0.4X, [%s])", base, formatMemory(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Formats data as a string of 2-digit hex bytes, separated by spaces.
|
* Formats data as a string of 2-digit hex bytes, separated by spaces.
|
||||||
*
|
*
|
||||||
|
@ -134,57 +241,6 @@ string formatMemory(const(ubyte[]) data, size_t max = 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct OpInfo
|
|
||||||
{
|
|
||||||
ushort addr;
|
|
||||||
ubyte data;
|
|
||||||
int len;
|
|
||||||
bool write;
|
|
||||||
}
|
|
||||||
|
|
||||||
alias void delegate(ubyte, CpuInfo, Block[], OpInfo, string, TestSetup*)
|
|
||||||
testsetup;
|
|
||||||
|
|
||||||
class TestSetup
|
|
||||||
{
|
|
||||||
testsetup setup;
|
|
||||||
TestSetup next;
|
|
||||||
|
|
||||||
auto static opCall(testsetup d)
|
|
||||||
{
|
|
||||||
auto obj = new TestSetup();
|
|
||||||
obj.setup = d;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
void run(ubyte opcode, CpuInfo cpu = CpuInfo(), Block[] data = [],
|
|
||||||
OpInfo info = OpInfo(), string msg = "")
|
|
||||||
{
|
|
||||||
setup(opcode, cpu, data, info, msg, &next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TestSetup connect(TestSetup first, TestSetup[] rest...)
|
|
||||||
{
|
|
||||||
if (!(rest.empty))
|
|
||||||
{
|
|
||||||
auto x = first;
|
|
||||||
while (x.next !is null) x = x.next;
|
|
||||||
x.next = connect(rest[0], rest[1..$]);
|
|
||||||
}
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
template testCallNext()
|
|
||||||
{
|
|
||||||
void callNext(string newMsg = "", Block[] newData = [])
|
|
||||||
{
|
|
||||||
if (*next !is null)
|
|
||||||
next.run(opcode, cpu, data ~ newData, info, msg ~ newMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Does nothing.
|
// Does nothing.
|
||||||
auto setup_none()
|
auto setup_none()
|
||||||
{
|
{
|
||||||
|
@ -1232,8 +1288,8 @@ auto setup_op_LSR(bool isAcc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For ASO.
|
// For SLO.
|
||||||
auto setup_op_ASO()
|
auto setup_op_SLO()
|
||||||
{
|
{
|
||||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||||
string msg, TestSetup* next)
|
string msg, TestSetup* next)
|
||||||
|
@ -1249,7 +1305,7 @@ auto setup_op_ASO()
|
||||||
callNext("acc 0x20 ");
|
callNext("acc 0x20 ");
|
||||||
}
|
}
|
||||||
return connect(TestSetup(&setup),
|
return connect(TestSetup(&setup),
|
||||||
setup_rmw(false, "ASO ", setup_asl_data()));
|
setup_rmw(false, "SLO ", setup_asl_data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// For RLA.
|
// For RLA.
|
||||||
|
@ -1270,8 +1326,8 @@ auto setup_op_RLA()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For LSE.
|
// For SRE.
|
||||||
auto setup_op_LSE()
|
auto setup_op_SRE()
|
||||||
{
|
{
|
||||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||||
string msg, TestSetup* next)
|
string msg, TestSetup* next)
|
||||||
|
@ -1284,7 +1340,7 @@ auto setup_op_LSE()
|
||||||
callNext("acc 0xFF ");
|
callNext("acc 0xFF ");
|
||||||
}
|
}
|
||||||
return connect(TestSetup(&setup),
|
return connect(TestSetup(&setup),
|
||||||
setup_rmw(false, "LSE ", setup_right_data()));
|
setup_rmw(false, "SRE ", setup_right_data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1382,8 +1438,8 @@ auto setup_op_SBC(bool cmos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For INS.
|
// For ISC.
|
||||||
auto setup_op_INS()
|
auto setup_op_ISC()
|
||||||
{
|
{
|
||||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||||
string msg, TestSetup* next)
|
string msg, TestSetup* next)
|
||||||
|
@ -1400,7 +1456,7 @@ auto setup_op_INS()
|
||||||
}
|
}
|
||||||
|
|
||||||
return connect(TestSetup(&setup), setup_flag(Flag.C), setup_flag(Flag.D),
|
return connect(TestSetup(&setup), setup_flag(Flag.C), setup_flag(Flag.D),
|
||||||
setup_rmw(false, "INS ", setup_inc_data()));
|
setup_rmw(false, "ISC ", setup_inc_data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1427,8 +1483,8 @@ auto setup_op_cmp(Reg reg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For DCM.
|
// For DCP.
|
||||||
auto setup_op_DCM()
|
auto setup_op_DCP()
|
||||||
{
|
{
|
||||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||||
string msg, TestSetup* next)
|
string msg, TestSetup* next)
|
||||||
|
@ -1445,7 +1501,7 @@ auto setup_op_DCM()
|
||||||
}
|
}
|
||||||
|
|
||||||
return connect(TestSetup(&setup), setup_flag(Flag.C),
|
return connect(TestSetup(&setup), setup_flag(Flag.C),
|
||||||
setup_rmw(false, "DCM ", setup_dec_data()));
|
setup_rmw(false, "DCP ", setup_dec_data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2105,8 +2161,8 @@ auto expect_LSR(bool isAcc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For ASO.
|
// For SLO.
|
||||||
auto expect_ASO()
|
auto expect_SLO()
|
||||||
{
|
{
|
||||||
void expect(ref Expected expected, const OpInfo info)
|
void expect(ref Expected expected, const OpInfo info)
|
||||||
{
|
{
|
||||||
|
@ -2138,8 +2194,8 @@ auto expect_RLA()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For LSE.
|
// For SRE.
|
||||||
auto expect_LSE()
|
auto expect_SRE()
|
||||||
{
|
{
|
||||||
void expect(ref Expected expected, const OpInfo info)
|
void expect(ref Expected expected, const OpInfo info)
|
||||||
{
|
{
|
||||||
|
@ -2306,8 +2362,8 @@ auto expect_SBC(bool cmos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For INS.
|
// For ISC.
|
||||||
auto expect_INS()
|
auto expect_ISC()
|
||||||
{
|
{
|
||||||
void expect(ref Expected expected, const OpInfo info)
|
void expect(ref Expected expected, const OpInfo info)
|
||||||
{
|
{
|
||||||
|
@ -2342,8 +2398,8 @@ auto expect_cmp(Reg reg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For DCM.
|
// For DCP.
|
||||||
auto expect_DCM()
|
auto expect_DCP()
|
||||||
{
|
{
|
||||||
void expect(ref Expected expected, const OpInfo info)
|
void expect(ref Expected expected, const OpInfo info)
|
||||||
{
|
{
|
||||||
|
@ -2510,12 +2566,12 @@ if (isCpu!T)
|
||||||
get_expect([0x04, 0x44, 0x64], "NOP");
|
get_expect([0x04, 0x44, 0x64], "NOP");
|
||||||
get_expect([0x14, 0x34, 0x54, 0x74, 0xD4, 0xF4], "NOP");
|
get_expect([0x14, 0x34, 0x54, 0x74, 0xD4, 0xF4], "NOP");
|
||||||
get_both([0x83, 0x87, 0x8F, 0x97], "SAX");
|
get_both([0x83, 0x87, 0x8F, 0x97], "SAX");
|
||||||
get_both([0x03, 0x07, 0x0F, 0x13, 0x17, 0x1B, 0x1F], "ASO");
|
get_both([0x03, 0x07, 0x0F, 0x13, 0x17, 0x1B, 0x1F], "SLO");
|
||||||
get_both([0x23, 0x27, 0x2F, 0x33, 0x37, 0x3B, 0x3F], "RLA");
|
get_both([0x23, 0x27, 0x2F, 0x33, 0x37, 0x3B, 0x3F], "RLA");
|
||||||
get_both([0x43, 0x47, 0x4F, 0x53, 0x57, 0x5B, 0x5F], "LSE");
|
get_both([0x43, 0x47, 0x4F, 0x53, 0x57, 0x5B, 0x5F], "SRE");
|
||||||
get_both([0x63, 0x67, 0x6F, 0x73, 0x77, 0x7B, 0x7F], "RRA");
|
get_both([0x63, 0x67, 0x6F, 0x73, 0x77, 0x7B, 0x7F], "RRA");
|
||||||
get_both([0xE3, 0xE7, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF], "INS");
|
get_both([0xE3, 0xE7, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF], "ISC");
|
||||||
get_both([0xC3, 0xC7, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF], "DCM");
|
get_both([0xC3, 0xC7, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF], "DCP");
|
||||||
get_both([0xEB], "SBC", "false");
|
get_both([0xEB], "SBC", "false");
|
||||||
|
|
||||||
// TODO: implement these opcode tests
|
// TODO: implement these opcode tests
|
||||||
|
@ -2671,7 +2727,7 @@ if (isCpu!T)
|
||||||
|
|
||||||
cycles = 2;
|
cycles = 2;
|
||||||
return [Bus(Action.READ, pc)] ~
|
return [Bus(Action.READ, pc)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+1)]);
|
[Bus(Action.READ, pc+1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2688,7 +2744,7 @@ if (isCpu!T)
|
||||||
|
|
||||||
cycles = 3;
|
cycles = 3;
|
||||||
return [Bus(Action.READ, pc)] ~
|
return [Bus(Action.READ, pc)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+1)]) ~
|
[Bus(Action.READ, pc+1)]) ~
|
||||||
[Bus(Action.WRITE, sp)];
|
[Bus(Action.WRITE, sp)];
|
||||||
}
|
}
|
||||||
|
@ -2707,7 +2763,7 @@ if (isCpu!T)
|
||||||
|
|
||||||
cycles = 4;
|
cycles = 4;
|
||||||
return [Bus(Action.READ, pc)] ~
|
return [Bus(Action.READ, pc)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+1),
|
[Bus(Action.READ, pc+1),
|
||||||
Bus(Action.READ, sp)]) ~
|
Bus(Action.READ, sp)]) ~
|
||||||
[Bus(Action.READ, sp1)];
|
[Bus(Action.READ, sp1)];
|
||||||
|
@ -2728,7 +2784,7 @@ if (isCpu!T)
|
||||||
cycles = 2 + decimal;
|
cycles = 2 + decimal;
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1)] ~
|
Bus(Action.READ, pc+1)] ~
|
||||||
If!decimal(If!(isStrict!T)(
|
If!decimal(If!(strict)(
|
||||||
[Bus(Action.READ, pc+2)]));
|
[Bus(Action.READ, pc+2)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2750,7 +2806,7 @@ if (isCpu!T)
|
||||||
cycles = 2 + branch + px;
|
cycles = 2 + branch + px;
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1)] ~
|
Bus(Action.READ, pc+1)] ~
|
||||||
If!branch(If!(isStrict!T)(
|
If!branch(If!(strict)(
|
||||||
[Bus(Action.READ, pc+2)] ~
|
[Bus(Action.READ, pc+2)] ~
|
||||||
If!px(
|
If!px(
|
||||||
[Bus(Action.READ, wrongAddr)])));
|
[Bus(Action.READ, wrongAddr)])));
|
||||||
|
@ -2805,7 +2861,7 @@ if (isCpu!T)
|
||||||
cycles = 3; // + accesses_end
|
cycles = 3; // + accesses_end
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1)] ~
|
Bus(Action.READ, pc+1)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
If!(isNMOS!T)(
|
If!(isNMOS!T)(
|
||||||
[Bus(Action.READ, op1)]) ~
|
[Bus(Action.READ, op1)]) ~
|
||||||
If!(isCMOS!T)(
|
If!(isCMOS!T)(
|
||||||
|
@ -2861,7 +2917,7 @@ if (isCpu!T)
|
||||||
cycles = 5; // + accesses_end
|
cycles = 5; // + accesses_end
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1)] ~
|
Bus(Action.READ, pc+1)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
If!(isNMOS!T)(
|
If!(isNMOS!T)(
|
||||||
[Bus(Action.READ, op1)]) ~
|
[Bus(Action.READ, op1)]) ~
|
||||||
If!(isCMOS!T)(
|
If!(isCMOS!T)(
|
||||||
|
@ -2962,14 +3018,14 @@ if (isCpu!T)
|
||||||
if (guess != right)
|
if (guess != right)
|
||||||
{
|
{
|
||||||
cycles += 1;
|
cycles += 1;
|
||||||
return If!(isStrict!T)(
|
return If!(strict)(
|
||||||
If!(isNMOS!T)([Bus(Action.READ, guess)]) ~
|
If!(isNMOS!T)([Bus(Action.READ, guess)]) ~
|
||||||
If!(isCMOS!T)([Bus(Action.READ, pc + opLen)])); // XXX
|
If!(isCMOS!T)([Bus(Action.READ, pc + opLen)])); // XXX
|
||||||
}
|
}
|
||||||
else if (noShortcut)
|
else if (noShortcut)
|
||||||
{
|
{
|
||||||
cycles += 1;
|
cycles += 1;
|
||||||
return If!(isStrict!T)([Bus(Action.READ, guess)]);
|
return If!(strict)([Bus(Action.READ, guess)]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2998,13 +3054,13 @@ if (isCpu!T)
|
||||||
cycles += (rmw ? 3 : (write ? 1 : (1 + decimal)));
|
cycles += (rmw ? 3 : (write ? 1 : (1 + decimal)));
|
||||||
return If!read(
|
return If!read(
|
||||||
[Bus(Action.READ, addr)] ~
|
[Bus(Action.READ, addr)] ~
|
||||||
If!decimal(If!(isStrict!T)(
|
If!decimal(If!(strict)(
|
||||||
[Bus(Action.READ, pc + opLen)]))) ~
|
[Bus(Action.READ, pc + opLen)]))) ~
|
||||||
If!write(
|
If!write(
|
||||||
[Bus(Action.WRITE, addr)]) ~
|
[Bus(Action.WRITE, addr)]) ~
|
||||||
If!rmw(
|
If!rmw(
|
||||||
[Bus(Action.READ, addr)] ~
|
[Bus(Action.READ, addr)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
If!(isNMOS!T)(
|
If!(isNMOS!T)(
|
||||||
[Bus(Action.WRITE, addr)]) ~
|
[Bus(Action.WRITE, addr)]) ~
|
||||||
If!(isCMOS!T)(
|
If!(isCMOS!T)(
|
||||||
|
@ -3028,12 +3084,12 @@ if (isCpu!T)
|
||||||
|
|
||||||
cycles = 6;
|
cycles = 6;
|
||||||
return [Bus(Action.READ, pc)] ~
|
return [Bus(Action.READ, pc)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+1),
|
[Bus(Action.READ, pc+1),
|
||||||
Bus(Action.READ, sp)]) ~
|
Bus(Action.READ, sp)]) ~
|
||||||
[Bus(Action.READ, sp1),
|
[Bus(Action.READ, sp1),
|
||||||
Bus(Action.READ, sp2)] ~
|
Bus(Action.READ, sp2)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, ret)]);
|
[Bus(Action.READ, ret)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3053,7 +3109,7 @@ if (isCpu!T)
|
||||||
|
|
||||||
cycles = 6;
|
cycles = 6;
|
||||||
return [Bus(Action.READ, pc)] ~
|
return [Bus(Action.READ, pc)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+1),
|
[Bus(Action.READ, pc+1),
|
||||||
Bus(Action.READ, sp)]) ~
|
Bus(Action.READ, sp)]) ~
|
||||||
[Bus(Action.READ, sp1),
|
[Bus(Action.READ, sp1),
|
||||||
|
@ -3076,7 +3132,7 @@ if (isCpu!T)
|
||||||
|
|
||||||
cycles = 7;
|
cycles = 7;
|
||||||
return [Bus(Action.READ, pc)] ~
|
return [Bus(Action.READ, pc)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+1)]) ~
|
[Bus(Action.READ, pc+1)]) ~
|
||||||
[Bus(Action.WRITE, sp),
|
[Bus(Action.WRITE, sp),
|
||||||
Bus(Action.WRITE, sp1),
|
Bus(Action.WRITE, sp1),
|
||||||
|
@ -3100,7 +3156,7 @@ if (isCpu!T)
|
||||||
cycles = 6;
|
cycles = 6;
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1)] ~
|
Bus(Action.READ, pc+1)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, sp)]) ~
|
[Bus(Action.READ, sp)]) ~
|
||||||
[Bus(Action.WRITE, sp),
|
[Bus(Action.WRITE, sp),
|
||||||
Bus(Action.WRITE, sp1),
|
Bus(Action.WRITE, sp1),
|
||||||
|
@ -3139,7 +3195,7 @@ if (isCpu!T)
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1),
|
Bus(Action.READ, pc+1),
|
||||||
Bus(Action.READ, pc+2)] ~
|
Bus(Action.READ, pc+2)] ~
|
||||||
If!(isStrict!T)(If!(isCMOS!T)(
|
If!(strict)(If!(isCMOS!T)(
|
||||||
[Bus(Action.READ, pc+3)])) ~ // XXX
|
[Bus(Action.READ, pc+3)])) ~ // XXX
|
||||||
[Bus(Action.READ, ial),
|
[Bus(Action.READ, ial),
|
||||||
Bus(Action.READ, iah)];
|
Bus(Action.READ, iah)];
|
||||||
|
@ -3169,7 +3225,7 @@ if (isCpu!T && isCMOS!T)
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1),
|
Bus(Action.READ, pc+1),
|
||||||
Bus(Action.READ, pc+2)] ~
|
Bus(Action.READ, pc+2)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+3)]) ~ // XXX
|
[Bus(Action.READ, pc+3)]) ~ // XXX
|
||||||
[Bus(Action.READ, ial),
|
[Bus(Action.READ, ial),
|
||||||
Bus(Action.READ, iah)];
|
Bus(Action.READ, iah)];
|
||||||
|
@ -3195,7 +3251,7 @@ if (isCpu!T && isCMOS!T)
|
||||||
cycles = 8;
|
cycles = 8;
|
||||||
return [Bus(Action.READ, pc),
|
return [Bus(Action.READ, pc),
|
||||||
Bus(Action.READ, pc+1)] ~
|
Bus(Action.READ, pc+1)] ~
|
||||||
If!(isStrict!T)(
|
If!(strict)(
|
||||||
[Bus(Action.READ, pc+2),
|
[Bus(Action.READ, pc+2),
|
||||||
Bus(Action.READ, weird),
|
Bus(Action.READ, weird),
|
||||||
Bus(Action.READ, 0xFFFF),
|
Bus(Action.READ, 0xFFFF),
|
||||||
|
@ -3334,3 +3390,59 @@ void test_opcode_timing(T)(ubyte opcode, busreport report)
|
||||||
auto run = connect(setup, run_timing_test!T(expected, report));
|
auto run = connect(setup, run_timing_test!T(expected, report));
|
||||||
run.run(opcode);
|
run.run(opcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct CheckOptions
|
||||||
|
{
|
||||||
|
enum Addr
|
||||||
|
{
|
||||||
|
IMP, IMM, ZP, ZPX, ZPY, IZX, IZY, ABS, ABX, ABY, IND, REL,
|
||||||
|
ZPI, ABI, NP1, NP8, KIL
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] opcodes;
|
||||||
|
ubyte[] codes6502;
|
||||||
|
ubyte[] codes65C02;
|
||||||
|
Addr[] addr;
|
||||||
|
|
||||||
|
this(string[] args)
|
||||||
|
{
|
||||||
|
getopt(args, "op", &opcodes, "addr", &addr);
|
||||||
|
foreach (op; opcodes)
|
||||||
|
{
|
||||||
|
if (op.startsWith("0x") || op.startsWith("0X"))
|
||||||
|
op = op[2..$];
|
||||||
|
if (isNumeric(op))
|
||||||
|
{
|
||||||
|
int code = to!int(op, 16);
|
||||||
|
if (code >= 0x00 && code <= 0xFF)
|
||||||
|
{
|
||||||
|
codes6502 ~= [cast(ubyte)code];
|
||||||
|
codes65C02 ~= [cast(ubyte)code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (code, name; OP_NAMES_6502)
|
||||||
|
if (name == op) codes6502 ~= [cast(ubyte)code];
|
||||||
|
foreach (code, name; OP_NAMES_65C02)
|
||||||
|
if (name == op) codes65C02 ~= [cast(ubyte)code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (a; addr)
|
||||||
|
{
|
||||||
|
foreach (code, mode; ADDR_MODES_6502)
|
||||||
|
if (a == mode) codes6502 ~= [cast(ubyte)code];
|
||||||
|
foreach (code, mode; ADDR_MODES_65C02)
|
||||||
|
if (a == mode) codes65C02 ~= [cast(ubyte)code];
|
||||||
|
}
|
||||||
|
if (opcodes.empty && addr.empty)
|
||||||
|
{
|
||||||
|
codes6502 = codes65C02 = new ubyte[256];
|
||||||
|
foreach (code; 0..256)
|
||||||
|
{
|
||||||
|
codes6502[code] = cast(ubyte)code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
207
test/cpu.d
207
test/cpu.d
|
@ -8,66 +8,71 @@ module test.cpu;
|
||||||
|
|
||||||
import std.conv, std.exception, std.random, std.string, std.traits;
|
import std.conv, std.exception, std.random, std.string, std.traits;
|
||||||
|
|
||||||
public import d6502.nmosundoc : NmosUndoc;
|
public import cpu.d6502 : Cpu, is6502, is65C02;
|
||||||
public import d6502.cmos : Cmos;
|
|
||||||
|
import test.base : strict, cumulative;
|
||||||
|
|
||||||
|
|
||||||
// True if T is the type of a cpu.
|
// True if T is the type of a cpu.
|
||||||
template isCpu(T)
|
template isCpu(T)
|
||||||
{
|
{
|
||||||
enum isCpu = __traits(hasMember, T, "_isCpuBase");
|
enum isCpu = __traits(hasMember, T, "_isCpu");
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if the cpu type T represents a 6502.
|
// True if the cpu type T represents a 6502.
|
||||||
template isNMOS(T)
|
template isNMOS(T)
|
||||||
{
|
{
|
||||||
enum isNMOS = __traits(hasMember, T, "_isNMOS");
|
enum isNMOS = is6502!T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if the cpu type T represents a 65C02.
|
// True if the cpu type T represents a 65C02.
|
||||||
template isCMOS(T)
|
template isCMOS(T)
|
||||||
{
|
{
|
||||||
enum isCMOS = __traits(hasMember, T, "_isCMOS");
|
enum isCMOS = is65C02!T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if the cpu type T accesses memory on every cycle.
|
|
||||||
template isStrict(T)
|
|
||||||
{
|
|
||||||
enum isStrict = __traits(hasMember, T, "_isStrict");
|
|
||||||
}
|
|
||||||
|
|
||||||
// True if the cpu type T aggregates ticks.
|
class TestIO
|
||||||
template isCumulative(T)
|
|
||||||
{
|
{
|
||||||
enum isCumulative = __traits(hasMember, T, "_isCumulative");
|
ubyte delegate(ushort) dread;
|
||||||
|
ubyte read(ushort addr) { return dread(addr); }
|
||||||
|
|
||||||
|
void delegate(ushort, ubyte) dwrite;
|
||||||
|
void write(ushort addr, ubyte val) { dwrite(addr, val); }
|
||||||
|
|
||||||
|
static if (cumulative)
|
||||||
|
{
|
||||||
|
void delegate(int) dtick;
|
||||||
|
void tick(int cycles) { dtick(cycles); }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
void delegate() dtick;
|
||||||
|
void tick() { dtick(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The type of a cpu, based on its architecture (6502 or 65C02) and
|
* The type of a cpu, based on its architecture (6502 or 65C02).
|
||||||
* its timing characteristics (strict or not bus access, cumulative or
|
|
||||||
* not cycle reporting).
|
|
||||||
*/
|
*/
|
||||||
template CPU(string arch, bool strict, bool cumulative)
|
template CPU(string arch, M = TestIO, C = TestIO)
|
||||||
{
|
{
|
||||||
static if (arch == "65c02" || arch == "65C02")
|
alias Cpu!(arch, M, C) CPU;
|
||||||
alias Cmos!(strict, cumulative) CPU;
|
|
||||||
else static if (arch == "6502")
|
|
||||||
alias NmosUndoc!(strict, cumulative) CPU;
|
|
||||||
else static assert(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto makeCpu(T)(CpuInfo info)
|
auto makeCpu(T)(CpuInfo info = CpuInfo())
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
auto cpu = new T();
|
auto tio = new TestIO();
|
||||||
cpu.programCounter = info.PC;
|
auto cpu = new T(tio, tio);
|
||||||
cpu.stackPointer = info.SP;
|
cpu.PC = info.PC;
|
||||||
cpu.flag.fromByte(info.S);
|
cpu.S = info.SP;
|
||||||
cpu.accumulator = info.A;
|
cpu.statusFromByte(info.S);
|
||||||
cpu.xIndex = info.X;
|
cpu.A = info.A;
|
||||||
cpu.yIndex = info.Y;
|
cpu.X = info.X;
|
||||||
|
cpu.Y = info.Y;
|
||||||
return cpu;
|
return cpu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +80,14 @@ if (isCpu!T)
|
||||||
void connectMem(T, S)(T cpu, ref S mem)
|
void connectMem(T, S)(T cpu, ref S mem)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
static if (isCumulative!T)
|
static if (cumulative)
|
||||||
void tick(int cycles) {}
|
void tick(int cycles) {}
|
||||||
else
|
else
|
||||||
void tick() {}
|
void tick() {}
|
||||||
|
|
||||||
cpu.memoryRead = &mem.read;
|
cpu.memory.dread = &mem.read;
|
||||||
cpu.memoryWrite = &mem.write;
|
cpu.memory.dwrite = &mem.write;
|
||||||
cpu.tick = &tick;
|
cpu.clock.dtick = &tick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,9 +104,9 @@ auto recordCycles(T)(T cpu)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
auto cycles = new int;
|
auto cycles = new int;
|
||||||
auto wrappedTick = cpu.tick;
|
auto wrappedTick = cpu.clock.dtick;
|
||||||
|
|
||||||
static if (isCumulative!T)
|
static if (cumulative)
|
||||||
{
|
{
|
||||||
void tick(int cyc)
|
void tick(int cyc)
|
||||||
{
|
{
|
||||||
|
@ -117,7 +122,7 @@ if (isCpu!T)
|
||||||
wrappedTick();
|
wrappedTick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cpu.tick = &tick;
|
cpu.clock.dtick = &tick;
|
||||||
|
|
||||||
return constRef(cycles);
|
return constRef(cycles);
|
||||||
}
|
}
|
||||||
|
@ -155,9 +160,9 @@ if (isCpu!T)
|
||||||
auto record = new Bus[actions];
|
auto record = new Bus[actions];
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
enforce(cpu.memoryRead !is null && cpu.memoryWrite !is null);
|
enforce(cpu.memory.dread !is null && cpu.memory.dwrite !is null);
|
||||||
auto wrappedRead = cpu.memoryRead;
|
auto wrappedRead = cpu.memory.dread;
|
||||||
auto wrappedWrite = cpu.memoryWrite;
|
auto wrappedWrite = cpu.memory.dwrite;
|
||||||
|
|
||||||
ubyte read(ushort addr)
|
ubyte read(ushort addr)
|
||||||
{
|
{
|
||||||
|
@ -177,8 +182,8 @@ if (isCpu!T)
|
||||||
wrappedWrite(addr, val);
|
wrappedWrite(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.memoryRead = &read;
|
cpu.memory.dread = &read;
|
||||||
cpu.memoryWrite = &write;
|
cpu.memory.dwrite = &write;
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
@ -210,8 +215,8 @@ enum Action : ushort { NONE, READ, WRITE }
|
||||||
void runUntilBRK(T)(T cpu)
|
void runUntilBRK(T)(T cpu)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
assert(cpu.memoryRead !is null);
|
assert(cpu.memory.dread !is null);
|
||||||
auto wrappedRead = cpu.memoryRead;
|
auto wrappedRead = cpu.memory.dread;
|
||||||
|
|
||||||
ubyte read(ushort addr)
|
ubyte read(ushort addr)
|
||||||
{
|
{
|
||||||
|
@ -219,7 +224,7 @@ if (isCpu!T)
|
||||||
return wrappedRead(addr);
|
return wrappedRead(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.memoryRead = &read;
|
cpu.memory.dread = &read;
|
||||||
|
|
||||||
try { cpu.run(true); } catch (StopException e) {}
|
try { cpu.run(true); } catch (StopException e) {}
|
||||||
}
|
}
|
||||||
|
@ -244,32 +249,27 @@ struct CpuInfo
|
||||||
|
|
||||||
string toString() const
|
string toString() const
|
||||||
{
|
{
|
||||||
return format("PC %0.4X SP %0.2X S %0.2X A %0.2X X %0.2X Y %0.2X",
|
return format("PC %0.4X SP %0.2X S %0.8b A %0.2X X %0.2X Y %0.2X",
|
||||||
PC, SP, S, A, X, Y);
|
PC, SP, S, A, X, Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static CpuInfo fromCpu(T)(T cpu)
|
static CpuInfo fromCpu(T)(T cpu)
|
||||||
{
|
{
|
||||||
CpuInfo info;
|
CpuInfo info;
|
||||||
info.PC = cpu.programCounter;
|
info.PC = cpu.PC;
|
||||||
info.SP = cpu.stackPointer;
|
info.SP = cpu.S;
|
||||||
info.A = cpu.accumulator;
|
info.A = cpu.A;
|
||||||
info.X = cpu.xIndex;
|
info.X = cpu.X;
|
||||||
info.Y = cpu.yIndex;
|
info.Y = cpu.Y;
|
||||||
info.S = cpu.flag.toByte();
|
info.S = cpu.statusToByte();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sets the program counter.
|
// Sets the program counter.
|
||||||
void setPC(T)(T cpu, int addr)
|
void setPC(T)(ref T cpu, int addr)
|
||||||
if (isCpu!T)
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
|
||||||
cpu.programCounter = cast(ushort)addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPC(T : CpuInfo)(ref T cpu, int addr)
|
|
||||||
{
|
{
|
||||||
cpu.PC = cast(ushort)addr;
|
cpu.PC = cast(ushort)addr;
|
||||||
}
|
}
|
||||||
|
@ -279,14 +279,10 @@ void incPC(T : CpuInfo)(ref T cpu, int amt = 1)
|
||||||
cpu.PC = pageCrossAdd(cpu.PC, amt);
|
cpu.PC = pageCrossAdd(cpu.PC, amt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the program counter.
|
|
||||||
ushort getPC(T)(T cpu)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
return cpu.programCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
ushort getPC(T : CpuInfo)(ref T cpu)
|
// Returns the program counter.
|
||||||
|
ushort getPC(T)(ref T cpu)
|
||||||
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
{
|
||||||
return cpu.PC;
|
return cpu.PC;
|
||||||
}
|
}
|
||||||
|
@ -302,7 +298,7 @@ void setSP(T)(T cpu, int val)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
assert(val < 0x0200);
|
assert(val < 0x0200);
|
||||||
cpu.stackPointer = cast(ubyte)val;
|
cpu.S = cast(ubyte)val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSP(T : CpuInfo)(ref T cpu, int val)
|
void setSP(T : CpuInfo)(ref T cpu, int val)
|
||||||
|
@ -329,7 +325,7 @@ void decSP(T : CpuInfo)(ref T cpu, int amt = -1)
|
||||||
ushort getSP(T)(T cpu)
|
ushort getSP(T)(T cpu)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
return 0x100 | cpu.stackPointer;
|
return 0x100 | cpu.S;
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort getSP(T : CpuInfo)(ref T cpu)
|
ushort getSP(T : CpuInfo)(ref T cpu)
|
||||||
|
@ -369,75 +365,48 @@ if (isCpu!T || is(T == CpuInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the A register.
|
// Sets the A register.
|
||||||
void setA(T)(T cpu, int val)
|
void setA(T)(ref T cpu, int val)
|
||||||
if (isCpu!T)
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
|
||||||
cpu.accumulator = cast(ubyte)val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setA(T : CpuInfo)(ref T cpu, int val)
|
|
||||||
{
|
{
|
||||||
cpu.A = cast(ubyte)val;
|
cpu.A = cast(ubyte)val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the A register.
|
|
||||||
ubyte getA(T)(T cpu, int val)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
return cpu.accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
ubyte getA(T : CpuInfo)(ref T cpu)
|
// Returns the A register.
|
||||||
|
ubyte getA(T)(ref T cpu)
|
||||||
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
{
|
||||||
return cpu.A;
|
return cpu.A;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sets the X register.
|
// Sets the X register.
|
||||||
void setX(T)(T cpu, int val)
|
void setX(T)(ref T cpu, int val)
|
||||||
if (isCpu!T)
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
|
||||||
cpu.xIndex = cast(ubyte)val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setX(T : CpuInfo)(ref T cpu, int val)
|
|
||||||
{
|
{
|
||||||
cpu.X = cast(ubyte)val;
|
cpu.X = cast(ubyte)val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the X register.
|
|
||||||
ubyte getX(T)(T cpu)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
return cpu.xIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ubyte getX(T : CpuInfo)(ref T cpu)
|
// Returns the X register.
|
||||||
|
ubyte getX(T)(ref T cpu)
|
||||||
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
{
|
||||||
return cpu.X;
|
return cpu.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sets the Y register.
|
// Sets the Y register.
|
||||||
void setY(T)(T cpu, int val)
|
void setY(T)(ref T cpu, int val)
|
||||||
if (isCpu!T)
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
|
||||||
cpu.yIndex = cast(ubyte)val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setY(T : CpuInfo)(ref T cpu, int val)
|
|
||||||
{
|
{
|
||||||
cpu.Y = cast(ubyte)val;
|
cpu.Y = cast(ubyte)val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the Y register.
|
|
||||||
ubyte getY(T)(T cpu)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
return cpu.yIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ubyte getY(T : CpuInfo)(ref T cpu)
|
// Returns the Y register.
|
||||||
|
ubyte getY(T)(ref T cpu)
|
||||||
|
if (isCpu!T || is(T == CpuInfo))
|
||||||
{
|
{
|
||||||
return cpu.Y;
|
return cpu.Y;
|
||||||
}
|
}
|
||||||
|
@ -472,9 +441,9 @@ string flagToString(Flag f)
|
||||||
void setFlag(T)(T cpu, Flag[] flags...)
|
void setFlag(T)(T cpu, Flag[] flags...)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
auto reg = cpu.flag.toByte();
|
auto reg = cpu.statusToByte();
|
||||||
foreach (flag; flags) reg |= flag;
|
foreach (flag; flags) reg |= flag;
|
||||||
cpu.flag.fromByte(reg);
|
cpu.statusFromByte(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
void setFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
||||||
|
@ -486,9 +455,9 @@ void setFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
||||||
void clearFlag(T)(T cpu, Flag[] flags...)
|
void clearFlag(T)(T cpu, Flag[] flags...)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
auto reg = cpu.flag.toByte();
|
auto reg = cpu.statusToByte();
|
||||||
foreach (flag; flags) reg &= ~flag;
|
foreach (flag; flags) reg &= ~flag;
|
||||||
cpu.flag.fromByte(reg);
|
cpu.statusFromByte(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
void clearFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
||||||
|
@ -500,7 +469,8 @@ void clearFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
||||||
bool getFlag(T)(T cpu, Flag f)
|
bool getFlag(T)(T cpu, Flag f)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
return (cpu.flag.toByte() & f) != 0;
|
return (cpu.statusToByte() & f) != 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getFlag(T : CpuInfo)(ref T cpu, Flag f)
|
bool getFlag(T : CpuInfo)(ref T cpu, Flag f)
|
||||||
|
@ -513,7 +483,7 @@ bool getFlag(T : CpuInfo)(ref T cpu, Flag f)
|
||||||
void setStatus(T)(T cpu, int val)
|
void setStatus(T)(T cpu, int val)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
cpu.flag.fromByte(cast(ubyte)val);
|
cpu.statusFromByte(cast(ubyte)val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStatus(T : CpuInfo)(ref T cpu, int val)
|
void setStatus(T : CpuInfo)(ref T cpu, int val)
|
||||||
|
@ -525,7 +495,8 @@ void setStatus(T : CpuInfo)(ref T cpu, int val)
|
||||||
ubyte getStatus(T)(T cpu)
|
ubyte getStatus(T)(T cpu)
|
||||||
if (isCpu!T)
|
if (isCpu!T)
|
||||||
{
|
{
|
||||||
return cpu.flag.toByte();
|
return cpu.statusToByte();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ubyte getStatus(T : CpuInfo)(ref T cpu)
|
ubyte getStatus(T : CpuInfo)(ref T cpu)
|
||||||
|
@ -604,8 +575,6 @@ if (isCpu!T || is(T == CpuInfo))
|
||||||
case /*BNE*/ 0xD0: setFlag(cpu, Flag.Z); break;
|
case /*BNE*/ 0xD0: setFlag(cpu, Flag.Z); break;
|
||||||
case /*BEQ*/ 0xF0: clearFlag(cpu, Flag.Z); break;
|
case /*BEQ*/ 0xF0: clearFlag(cpu, Flag.Z); break;
|
||||||
default:
|
default:
|
||||||
if (isCMOS!T)
|
|
||||||
enforce(opcode != 0x80, "BRA can never not branch");
|
|
||||||
enforce(0, format("not a branching opcpde %0.2X", opcode));
|
enforce(0, format("not a branching opcpde %0.2X", opcode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
import std.array, std.exception, std.getopt, std.process, std.stdio,
|
||||||
|
std.traits;
|
||||||
|
|
||||||
|
|
||||||
|
enum OpDefs
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Delegates = 1,
|
||||||
|
Switch = 2,
|
||||||
|
NestedSwitch = 4,
|
||||||
|
All = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Tests
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Func = 1,
|
||||||
|
Bus = 2,
|
||||||
|
Dec = 4,
|
||||||
|
All = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
string[OpDefs] defStrings;
|
||||||
|
string[Tests] fNames;
|
||||||
|
|
||||||
|
static this()
|
||||||
|
{
|
||||||
|
fNames = [
|
||||||
|
Tests.Func:" test_func.d ",
|
||||||
|
Tests.Bus:" test_bus.d ",
|
||||||
|
Tests.Dec:" test_decimal.d "
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
version(DigitalMars)
|
||||||
|
{
|
||||||
|
static this()
|
||||||
|
{
|
||||||
|
defStrings = [
|
||||||
|
OpDefs.Delegates:" -version=OpDelegates",
|
||||||
|
OpDefs.Switch:" -version=OpSwitch",
|
||||||
|
OpDefs.NestedSwitch:" -version=OpNestedSwitch"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
string[] stStrings = [" ", " -version=Strict"];
|
||||||
|
string[] cmStrings = [" ", " -version=Cumulative"];
|
||||||
|
}
|
||||||
|
else version(GNU)
|
||||||
|
{
|
||||||
|
static assert(false, "TODO: add support for GDC.");
|
||||||
|
}
|
||||||
|
else version(LDC)
|
||||||
|
{
|
||||||
|
static assert(false, "TODO: add support for LDC.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
static assert(false, "Unknown compiler.");
|
||||||
|
|
||||||
|
|
||||||
|
OpDefs opdefs;
|
||||||
|
bool strict, cumulative;
|
||||||
|
Tests tests;
|
||||||
|
bool help;
|
||||||
|
|
||||||
|
OpDefs[] deflist;
|
||||||
|
Tests[] testlist;
|
||||||
|
|
||||||
|
void main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.length == 1)
|
||||||
|
writeln("(running default tests; use --help for options)");
|
||||||
|
|
||||||
|
getopt(args,
|
||||||
|
std.getopt.config.passThrough,
|
||||||
|
"def", &deflist,
|
||||||
|
"test", &testlist,
|
||||||
|
"help", &help);
|
||||||
|
|
||||||
|
if (help)
|
||||||
|
{
|
||||||
|
writeln(
|
||||||
|
`Options:
|
||||||
|
--test=type Func, Bus, Dec, or All
|
||||||
|
--def=style Delegates, Switch, or NestedSwitch
|
||||||
|
--op=num test opcode 'num' (num is hex)
|
||||||
|
--op=name test all opcodes named 'name'
|
||||||
|
--addr=mode test all opcodes with addressing mode 'mode'
|
||||||
|
|
||||||
|
(All options con be specified multiple times.
|
||||||
|
--op and --addr have no effect on decimal mode tests.)`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(def; deflist) opdefs |= def;
|
||||||
|
foreach(test; testlist) tests |= test;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runTests(args);
|
||||||
|
}
|
||||||
|
catch (ErrnoException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTests(string[] args)
|
||||||
|
{
|
||||||
|
// If no opdef specified, use Delegates.
|
||||||
|
if (opdefs == OpDefs.None) opdefs = OpDefs.Delegates;
|
||||||
|
|
||||||
|
int defcount;
|
||||||
|
foreach (def; EnumMembers!OpDefs)
|
||||||
|
if ((opdefs & def) && def != OpDefs.All) defcount++;
|
||||||
|
|
||||||
|
// If no tests specified, run all (but exclude Dec by default if
|
||||||
|
// running with more than one opdef).
|
||||||
|
if (tests == Tests.None)
|
||||||
|
tests = Tests.Func | Tests.Bus;
|
||||||
|
if (!defcount) tests |= Tests.Dec;
|
||||||
|
|
||||||
|
foreach (def; EnumMembers!OpDefs)
|
||||||
|
if ((opdefs & def) && def != OpDefs.All)
|
||||||
|
foreach (test; EnumMembers!Tests)
|
||||||
|
if ((tests & test) && test != Tests.All)
|
||||||
|
runTest(def, test, args[1..$]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTest(OpDefs def, Tests test, string[] args)
|
||||||
|
{
|
||||||
|
writeln("Using ", defStrings[def]);
|
||||||
|
foreach (s; [false, true])
|
||||||
|
{
|
||||||
|
foreach (c; [false, true])
|
||||||
|
{
|
||||||
|
writeln("With strict=", s, " cumulative=", c);
|
||||||
|
string cmdline = defStrings[def] ~ stStrings[s] ~ cmStrings[c] ~
|
||||||
|
fNames[test] ~ join(args, " ");
|
||||||
|
system("rdmd --force -I.. -I../src -version=RunTest " ~ cmdline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +1,20 @@
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
import test.base, test.cpu;
|
import test.base, test.cpu;
|
||||||
|
|
||||||
|
|
||||||
void main()
|
void main(string[] args)
|
||||||
{
|
{
|
||||||
|
auto opts = CheckOptions(args);
|
||||||
auto report = report_timing_debug();
|
auto report = report_timing_debug();
|
||||||
|
|
||||||
alias CPU!("65C02", false, false) T1;
|
alias CPU!("6502") T1;
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
writeln("Testing bus/timing, 6502");
|
||||||
|
foreach (op; opts.codes6502)
|
||||||
test_opcode_timing!T1(cast(ubyte)op, report);
|
test_opcode_timing!T1(cast(ubyte)op, report);
|
||||||
|
|
||||||
alias CPU!("65C02", true, false) T2;
|
alias CPU!("65C02") T2;
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
writeln("Testing bus/timing, 65C02");
|
||||||
|
foreach (op; opts.codes65C02)
|
||||||
test_opcode_timing!T2(cast(ubyte)op, report);
|
test_opcode_timing!T2(cast(ubyte)op, report);
|
||||||
|
|
||||||
alias CPU!("6502", false, false) T3;
|
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
|
||||||
test_opcode_timing!T3(cast(ubyte)op, report);
|
|
||||||
|
|
||||||
alias CPU!("6502", true, false) T4;
|
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
|
||||||
test_opcode_timing!T4(cast(ubyte)op, report);
|
|
||||||
|
|
||||||
alias CPU!("65C02", false, true) T5;
|
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
|
||||||
test_opcode_timing!T5(cast(ubyte)op, report);
|
|
||||||
|
|
||||||
alias CPU!("65C02", true, true) T6;
|
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
|
||||||
test_opcode_timing!T6(cast(ubyte)op, report);
|
|
||||||
|
|
||||||
alias CPU!("6502", false, true) T7;
|
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
|
||||||
test_opcode_timing!T7(cast(ubyte)op, report);
|
|
||||||
|
|
||||||
alias CPU!("6502", true, true) T8;
|
|
||||||
for (int op = 0x00; op < 0x100; op++)
|
|
||||||
test_opcode_timing!T8(cast(ubyte)op, report);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
rdmd @testopts test_func.d
|
|
||||||
rdmd @testopts test_bus.d
|
|
||||||
rdmd @testopts test_decimal.d
|
|
||||||
|
|
|
@ -211,10 +211,21 @@ if (isCpu!T)
|
||||||
mem.write(0x8055, 0x84);
|
mem.write(0x8055, 0x84);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cpu = new T();
|
version(Benchmark)
|
||||||
connectMem(cpu, mem);
|
{
|
||||||
|
auto runner = new BreakRunner(mem);
|
||||||
|
auto cpu = new T(runner, runner);
|
||||||
|
runner.keepRunning = &cpu.keepRunning;
|
||||||
setPC(cpu, 0x8000);
|
setPC(cpu, 0x8000);
|
||||||
|
cpu.run(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto cpu = makeCpu!T();
|
||||||
|
setPC(cpu, 0x8000);
|
||||||
|
connectMem(cpu, mem);
|
||||||
runUntilBRK(cpu);
|
runUntilBRK(cpu);
|
||||||
|
}
|
||||||
if (mem[0x8003])
|
if (mem[0x8003])
|
||||||
{
|
{
|
||||||
// TODO: check data block to find out what failed exactly
|
// TODO: check data block to find out what failed exactly
|
||||||
|
@ -226,9 +237,43 @@ if (isCpu!T)
|
||||||
version(Benchmark)
|
version(Benchmark)
|
||||||
{
|
{
|
||||||
import std.datetime, std.stdio;
|
import std.datetime, std.stdio;
|
||||||
|
|
||||||
|
final class BreakRunner
|
||||||
|
{
|
||||||
|
TestMemory* mem;
|
||||||
|
bool* keepRunning;
|
||||||
|
|
||||||
|
this(ref TestMemory mem)
|
||||||
|
{
|
||||||
|
this.mem = &mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ubyte read(ushort addr)
|
||||||
|
{
|
||||||
|
if (addr == 0xfffe)
|
||||||
|
{
|
||||||
|
*keepRunning = false;
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
else if (addr == 0xffff)
|
||||||
|
{
|
||||||
|
return 0x80;
|
||||||
|
}
|
||||||
|
else return mem.read(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
final void write(ushort addr, ubyte val)
|
||||||
|
{
|
||||||
|
mem.write(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static if (cumulative) { final void tick(int) {} }
|
||||||
|
else { final void tick() {} }
|
||||||
|
}
|
||||||
|
|
||||||
void f0()
|
void f0()
|
||||||
{
|
{
|
||||||
testDecimalMode!(CPU!("65C02", false, false))();
|
testDecimalMode!(CPU!("65C02", BreakRunner, BreakRunner))();
|
||||||
}
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
|
@ -243,24 +288,10 @@ else
|
||||||
{
|
{
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.no)");
|
writeln("Testing decimal mode, 6502");
|
||||||
testDecimalMode!(CPU!("6502", false, false))();
|
testDecimalMode!(CPU!("6502"))();
|
||||||
writeln("Testing decimal mode, CMOS(Strict.no, Cumulative.no)");
|
|
||||||
testDecimalMode!(CPU!("65C02", false, false))();
|
|
||||||
|
|
||||||
writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.yes)");
|
writeln("Testing decimal mode, 65C02");
|
||||||
testDecimalMode!(CPU!("6502", false, true))();
|
testDecimalMode!(CPU!("65C02"))();
|
||||||
writeln("Testing decimal mode, CMOS(Strict.no, Cumulative.yes)");
|
|
||||||
testDecimalMode!(CPU!("65C02", false, true))();
|
|
||||||
|
|
||||||
writeln("Testing decimal mode, NMOS(Strict.yes, Cumulative.no)");
|
|
||||||
testDecimalMode!(CPU!("6502", true, false))();
|
|
||||||
writeln("Testing decimal mode, CMOS(Strict.yes, Cumulative.no)");
|
|
||||||
testDecimalMode!(CPU!("65C02", true, false))();
|
|
||||||
|
|
||||||
writeln("Testing decimal mode, NMOS(Strict.yes, Cumulative.yes)");
|
|
||||||
testDecimalMode!(CPU!("6502", true, true))();
|
|
||||||
writeln("Testing decimal mode, CMOS(Strict.yes, Cumulative.yes)");
|
|
||||||
testDecimalMode!(CPU!("65C02", true, true))();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
import test.base, test.cpu;
|
import test.base, test.cpu;
|
||||||
|
|
||||||
|
|
||||||
void main()
|
void main(string[] args)
|
||||||
{
|
{
|
||||||
|
auto opts = CheckOptions(args);
|
||||||
auto report = report_debug();
|
auto report = report_debug();
|
||||||
|
|
||||||
alias CPU!("65C02", false, false) T1;
|
alias CPU!("6502") T1;
|
||||||
foreach (opcode; 0..255)
|
writeln("Testing functionality, 6502");
|
||||||
|
foreach (opcode; opts.codes6502)
|
||||||
test_one_opcode!T1(cast(ubyte)opcode, report);
|
test_one_opcode!T1(cast(ubyte)opcode, report);
|
||||||
|
|
||||||
alias CPU!("6502", false, false) T2;
|
alias CPU!("65C02") T2;
|
||||||
foreach (opcode; 0..255)
|
writeln("Testing functionality, 65C02");
|
||||||
|
foreach (opcode; opts.codes65C02)
|
||||||
test_one_opcode!T2(cast(ubyte)opcode, report);
|
test_one_opcode!T2(cast(ubyte)opcode, report);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue