mirror of
https://github.com/edmccard/twoapple-reboot.git
synced 2024-06-14 10:29:31 +00:00
372 lines
10 KiB
D
372 lines
10 KiB
D
|
import std.stdio, std.string, std.exception;
|
||
|
|
||
|
import test.d6502.base, test.d6502.cpu;
|
||
|
|
||
|
|
||
|
auto setup_vectors(T)(bool res, bool nmi, bool irq)
|
||
|
{
|
||
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||
|
string msg, TestSetup* next)
|
||
|
{
|
||
|
mixin testCallNext;
|
||
|
setPC(cpu, 0xfd00);
|
||
|
auto ndata = [Block(0xfd00, [0xea, 0xea]),
|
||
|
Block(0xfe35, [0xea, 0xea])];
|
||
|
if (res)
|
||
|
callNext("RESET ", ndata ~
|
||
|
[Block(0xfffa, [0x00, 0xff, 0x35, 0xfe, 0x80, 0xff])]);
|
||
|
else if (nmi)
|
||
|
callNext("NMI ", ndata ~
|
||
|
[Block(0xfffa, [0x35, 0xfe, 0x40, 0xff, 0x80, 0xff])]);
|
||
|
else if (irq && !getFlag(cpu, Flag.I))
|
||
|
callNext("IRQ ", ndata ~
|
||
|
[Block(0xfffa, [0x00, 0xff, 0x40, 0xff, 0x35, 0xfe])]);
|
||
|
else
|
||
|
callNext("NOP ", ndata);
|
||
|
}
|
||
|
|
||
|
return connect(setup_mask_flags(), setup_flag(Flag.I), TestSetup(&setup));
|
||
|
}
|
||
|
|
||
|
|
||
|
auto accesses_reset(T)(T cpu, ref TestMemory mem, out int cycles)
|
||
|
if (isCpu!T)
|
||
|
{
|
||
|
auto pc = getPC(cpu);
|
||
|
auto sp = getSP(cpu);
|
||
|
auto sp1 = pageWrapAdd(sp, -1);
|
||
|
auto sp2 = pageWrapAdd(sp, -2);
|
||
|
|
||
|
cycles = 7;
|
||
|
return If!(strict)(
|
||
|
[Bus(Action.READ, pc),
|
||
|
Bus(Action.READ, pc),
|
||
|
Bus(Action.READ, sp),
|
||
|
Bus(Action.READ, sp1),
|
||
|
Bus(Action.READ, sp2)]) ~
|
||
|
[Bus(Action.READ, 0xfffc),
|
||
|
Bus(Action.READ, 0xfffd)];
|
||
|
}
|
||
|
|
||
|
auto accesses_nmi(T)(T cpu, ref TestMemory mem, out int cycles)
|
||
|
if (isCpu!T)
|
||
|
{
|
||
|
auto pc = getPC(cpu);
|
||
|
auto sp = getSP(cpu);
|
||
|
auto sp1 = pageWrapAdd(sp, -1);
|
||
|
auto sp2 = pageWrapAdd(sp, -2);
|
||
|
|
||
|
cycles = 7;
|
||
|
return If!(strict)(
|
||
|
[Bus(Action.READ, pc),
|
||
|
Bus(Action.READ, pc)]) ~
|
||
|
[Bus(Action.WRITE, sp),
|
||
|
Bus(Action.WRITE, sp1),
|
||
|
Bus(Action.WRITE, sp2),
|
||
|
Bus(Action.READ, 0xfffa),
|
||
|
Bus(Action.READ, 0xfffb)];
|
||
|
}
|
||
|
|
||
|
auto accesses_irq(T)(T cpu, ref TestMemory mem, out int cycles)
|
||
|
if (isCpu!T)
|
||
|
{
|
||
|
auto pc = getPC(cpu);
|
||
|
auto sp = getSP(cpu);
|
||
|
auto sp1 = pageWrapAdd(sp, -1);
|
||
|
auto sp2 = pageWrapAdd(sp, -2);
|
||
|
|
||
|
cycles = 7;
|
||
|
return If!(strict)(
|
||
|
[Bus(Action.READ, pc),
|
||
|
Bus(Action.READ, pc)]) ~
|
||
|
[Bus(Action.WRITE, sp),
|
||
|
Bus(Action.WRITE, sp1),
|
||
|
Bus(Action.WRITE, sp2),
|
||
|
Bus(Action.READ, 0xfffe),
|
||
|
Bus(Action.READ, 0xffff)];
|
||
|
}
|
||
|
|
||
|
|
||
|
auto check_signal_timing(T)(bool res, bool nmi, bool irq, busreport report)
|
||
|
{
|
||
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||
|
string msg, TestSetup* next)
|
||
|
{
|
||
|
mixin testCallNext;
|
||
|
auto testcpu = makeCpu!T(cpu);
|
||
|
auto mem = TestMemory(data);
|
||
|
|
||
|
if (res) testcpu.signals.triggerReset();
|
||
|
if (nmi) testcpu.signals.triggerNMI();
|
||
|
if (irq) testcpu.signals.assertIRQ();
|
||
|
|
||
|
int expCycles, dummy;
|
||
|
Bus[] expBus;
|
||
|
auto accesses_after = [Bus(Action.READ, 0xfe35)] ~
|
||
|
If!(strict)([Bus(Action.READ, 0xfe36)]);
|
||
|
if (res)
|
||
|
expBus = accesses_reset(testcpu, mem, expCycles) ~
|
||
|
accesses_after;
|
||
|
else if (nmi)
|
||
|
expBus = accesses_nmi(testcpu, mem, expCycles) ~
|
||
|
accesses_after;
|
||
|
else if (irq && !getFlag(testcpu, Flag.I))
|
||
|
expBus = accesses_irq(testcpu, mem, expCycles) ~
|
||
|
accesses_after;
|
||
|
else
|
||
|
expBus = accesses_reg(testcpu, mem, dummy);
|
||
|
expCycles += 2;
|
||
|
expBus = expBus ~ new Bus[9 - expBus.length];
|
||
|
|
||
|
connectMem(testcpu, mem);
|
||
|
auto actualBus = recordBus(testcpu, 9);
|
||
|
auto actualCycles = recordCycles(testcpu);
|
||
|
|
||
|
runOneOpcode(testcpu);
|
||
|
|
||
|
report(actualCycles, actualBus, expCycles, expBus,
|
||
|
opcode, T.stringof ~ " | " ~ msg);
|
||
|
callNext();
|
||
|
}
|
||
|
return TestSetup(&setup);
|
||
|
}
|
||
|
|
||
|
|
||
|
auto expect_int(T)(bool res, bool nmi, bool irq)
|
||
|
{
|
||
|
void expect(ref Expected expected, const OpInfo info)
|
||
|
{
|
||
|
with(expected)
|
||
|
{
|
||
|
if (res || nmi || (irq && !getFlag(cpu, Flag.I)))
|
||
|
{
|
||
|
ushort ret = cast(ushort)(getPC(cpu));
|
||
|
if (!res)
|
||
|
mem[getSP(cpu)] = addrHi(ret);
|
||
|
decSP(cpu);
|
||
|
if (!res)
|
||
|
mem[getSP(cpu)] = addrLo(ret);
|
||
|
decSP(cpu);
|
||
|
if (!res)
|
||
|
mem[getSP(cpu)] = getStatus(cpu) & ~0x10;
|
||
|
decSP(cpu);
|
||
|
setPC(cpu, 0xfe36);
|
||
|
setFlag(cpu, Flag.I);
|
||
|
if (isCMOS!T) clearFlag(cpu, Flag.D);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
setPC(cpu, 0xfd01);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return &expect;
|
||
|
}
|
||
|
|
||
|
|
||
|
auto check_signal(T)(bool res, bool nmi, bool irq, testreport report)
|
||
|
{
|
||
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||
|
string msg, TestSetup* next)
|
||
|
{
|
||
|
mixin testCallNext;
|
||
|
auto testcpu = makeCpu!T(cpu);
|
||
|
auto mem = TestMemory(data);
|
||
|
auto expected = Expected(cpu, mem);
|
||
|
expect_int!T(res, nmi, irq)(expected, info);
|
||
|
connectMem(testcpu, mem);
|
||
|
|
||
|
if (res) testcpu.signals.triggerReset();
|
||
|
if (nmi) testcpu.signals.triggerNMI();
|
||
|
if (irq) testcpu.signals.assertIRQ();
|
||
|
|
||
|
runOneOpcode(testcpu);
|
||
|
auto cpuResult = CpuInfo.fromCpu(testcpu);
|
||
|
report(opcode, expected, cpuResult, mem, T.stringof ~ " | " ~ msg);
|
||
|
callNext();
|
||
|
}
|
||
|
return TestSetup(&setup);
|
||
|
}
|
||
|
|
||
|
|
||
|
void test_signals(T)()
|
||
|
{
|
||
|
void foo(T)(bool res, bool nmi, bool irq)
|
||
|
{
|
||
|
auto run = connect(setup_vectors!T(res, nmi, irq),
|
||
|
check_signal_timing!T(res, nmi, irq, report_timing_debug()));
|
||
|
run.run(0xea);
|
||
|
}
|
||
|
|
||
|
void bar(T)(bool res, bool nmi, bool irq)
|
||
|
{
|
||
|
auto run = connect(setup_vectors!T(res, nmi, irq),
|
||
|
check_signal!T(res, nmi, irq, report_debug()));
|
||
|
run.run(0xea);
|
||
|
}
|
||
|
|
||
|
foreach (res; [false, true])
|
||
|
{
|
||
|
foreach (nmi; [false, true])
|
||
|
{
|
||
|
foreach (irq; [false, true])
|
||
|
{
|
||
|
foo!T(res, nmi, irq);
|
||
|
bar!T(res, nmi, irq);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void test_cli_delay(T)()
|
||
|
{
|
||
|
void run_test(ubyte opcode2, ushort delayPC, string name)
|
||
|
{
|
||
|
auto mem = TestMemory(Block(0xfd00, [0x58, opcode2, 0xea]),
|
||
|
Block(0xfe35, [0xea, 0xea, 0xea]),
|
||
|
Block(0xfffe, [0x35, 0xfe]));
|
||
|
auto cpu = makeCpu!T();
|
||
|
setPC(cpu, 0xfd00);
|
||
|
setFlag(cpu, Flag.I);
|
||
|
cpu.signals.assertIRQ();
|
||
|
connectMem(cpu, mem);
|
||
|
runOneOpcode(cpu);
|
||
|
runOneOpcode(cpu);
|
||
|
runOneOpcode(cpu);
|
||
|
|
||
|
static if (isCMOS!T) { auto expPC = 0xfe37; }
|
||
|
else { auto expPC = delayPC; }
|
||
|
|
||
|
if (getPC(cpu) != expPC)
|
||
|
{
|
||
|
writeln(format(name ~ " expected pc $%0.4x got $%0.4x",
|
||
|
expPC, getPC(cpu)));
|
||
|
throw new TestException("cli_delay_1");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
run_test(0xea, 0xfe36, "CLI-delay");
|
||
|
run_test(0x78, 0xfd03, "CLI-delay-allows-SEI");
|
||
|
run_test(0x58, 0xfe36, "multiple-CLI");
|
||
|
}
|
||
|
|
||
|
void test_plp_delay(T)()
|
||
|
{
|
||
|
void run_test(ubyte opcode2, ushort delayPC, string name)
|
||
|
{
|
||
|
auto mem = TestMemory(Block(0x1fe, [0x00, 0x00]),
|
||
|
Block(0xfd00, [0x28, opcode2, 0xea]),
|
||
|
Block(0xfe35, [0xea, 0xea, 0xea]),
|
||
|
Block(0xfffe, [0x35, 0xfe]));
|
||
|
auto cpu = makeCpu!T();
|
||
|
setPC(cpu, 0xfd00);
|
||
|
setSP(cpu, 0x01fd);
|
||
|
setFlag(cpu, Flag.I);
|
||
|
cpu.signals.assertIRQ();
|
||
|
connectMem(cpu, mem);
|
||
|
runOneOpcode(cpu);
|
||
|
runOneOpcode(cpu);
|
||
|
runOneOpcode(cpu);
|
||
|
|
||
|
static if (isCMOS!T) { auto expPC = 0xfe37; }
|
||
|
else { auto expPC = delayPC; }
|
||
|
|
||
|
if (getPC(cpu) != expPC)
|
||
|
{
|
||
|
writeln(format(name ~ " expected pc $%0.4x got $%0.4x",
|
||
|
expPC, getPC(cpu)));
|
||
|
throw new TestException("cli_delay_1");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
run_test(0xea, 0xfe36, "PLP-delay");
|
||
|
run_test(0x78, 0xfd03, "PLP-delay-allows-SEI");
|
||
|
run_test(0x28, 0xfe36, "multiple-PLP");
|
||
|
}
|
||
|
|
||
|
void test_sei_interruptable(T)()
|
||
|
{
|
||
|
auto mem = TestMemory(Block(0xfd00, [0x78, 0xea, 0xea]),
|
||
|
Block(0xfe35, [0xea, 0xea, 0xea]),
|
||
|
Block(0xfffe, [0x35, 0xfe]));
|
||
|
auto cpu = makeCpu!T();
|
||
|
setPC(cpu, 0xfd00);
|
||
|
clearFlag(cpu, Flag.I);
|
||
|
cpu.signals.assertIRQ();
|
||
|
connectMem(cpu, mem);
|
||
|
runOneOpcode(cpu);
|
||
|
|
||
|
if (getPC(cpu) != 0xfe36)
|
||
|
{
|
||
|
writeln(format("SEI expected pc $fe36 got $%0.4x", getPC(cpu)));
|
||
|
throw new TestException("cli_delay_1");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void test_nmi_brk(T)()
|
||
|
{
|
||
|
class RunTest
|
||
|
{
|
||
|
uint ticks, nmiTick;
|
||
|
T cpu;
|
||
|
void tick()
|
||
|
{
|
||
|
if (ticks == nmiTick) cpu.signals.triggerNMI();
|
||
|
ticks++;
|
||
|
}
|
||
|
this(uint nmiTick)
|
||
|
{
|
||
|
this.nmiTick = nmiTick;
|
||
|
auto mem = TestMemory(Block(0xfd00, [0x00, 0xea]),
|
||
|
Block(0xfe35, [0xea]),
|
||
|
Block(0xff45, [0xea]),
|
||
|
Block(0xfffa, [0x45, 0xff]),
|
||
|
Block(0xfffe, [0x35, 0xfe]));
|
||
|
cpu = makeCpu!T();
|
||
|
setPC(cpu, 0xfd00);
|
||
|
connectMem(cpu, mem);
|
||
|
cpu.clock.dtick = &tick;
|
||
|
runOneOpcode(cpu);
|
||
|
ushort expPC;
|
||
|
if (nmiTick < 5 && isNMOS!T)
|
||
|
expPC = 0xff45;
|
||
|
else
|
||
|
expPC = 0xfe35;
|
||
|
if (getPC(cpu) != expPC)
|
||
|
{
|
||
|
writeln(format("nmi-brk tick %d expected $%0.4x got $%0.4x",
|
||
|
nmiTick, expPC, getPC(cpu)));
|
||
|
throw new TestException("nmi-brk");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (t; 0..6)
|
||
|
{
|
||
|
auto dummy = new RunTest(t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void main()
|
||
|
{
|
||
|
alias CPU!("6502") T1;
|
||
|
writeln("Testing signals, 6502");
|
||
|
test_signals!T1();
|
||
|
test_cli_delay!T1();
|
||
|
test_plp_delay!T1();
|
||
|
test_sei_interruptable!T1();
|
||
|
version(Cumulative) {}
|
||
|
else { test_nmi_brk!T1(); }
|
||
|
|
||
|
alias CPU!("65C02") T2;
|
||
|
writeln("Testing signals, 65C02");
|
||
|
test_signals!T2();
|
||
|
test_cli_delay!T2();
|
||
|
test_plp_delay!T2();
|
||
|
test_sei_interruptable!T2();
|
||
|
version(Cumulative) {}
|
||
|
else { test_nmi_brk!T2(); }
|
||
|
}
|