mirror of
https://github.com/edmccard/twoapple-reboot.git
synced 2024-06-10 00:29:52 +00:00
Decouple test sstup code from test running code
This commit is contained in:
parent
b0ae43067c
commit
ceb7f5b678
771
test/base.d
771
test/base.d
|
@ -1,7 +1,8 @@
|
||||||
module test.base;
|
module test.base;
|
||||||
|
|
||||||
|
|
||||||
import std.algorithm, std.conv, std.exception, std.range, std.string;
|
import std.algorithm, std.array, std.conv, std.exception, std.stdio,
|
||||||
|
std.string;
|
||||||
|
|
||||||
import test.cpu, test.opcodes;
|
import test.cpu, test.opcodes;
|
||||||
|
|
||||||
|
@ -2562,3 +2563,771 @@ if (isCpu!T)
|
||||||
}
|
}
|
||||||
return ret ~ "\n}";
|
return ret ~ "\n}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
alias void delegate(ubyte, ref Expected, CpuInfo, const ref TestMemory, string)
|
||||||
|
testreport;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Runs one opcode. Calls expect for the expected values of the cpu
|
||||||
|
* registers and memory. Calls report with the expected and actual
|
||||||
|
* values.
|
||||||
|
*/
|
||||||
|
auto run_opcode_test(T)(testexpect expect, 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(expected, info);
|
||||||
|
connectMem(testcpu, mem);
|
||||||
|
runOneOpcode(testcpu);
|
||||||
|
auto cpuResult = CpuInfo.fromCpu(testcpu);
|
||||||
|
report(opcode, expected, cpuResult, mem, T.stringof ~ " | " ~ msg);
|
||||||
|
callNext();
|
||||||
|
}
|
||||||
|
return TestSetup(&setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Dummy function. Reports nothing.
|
||||||
|
auto report_none()
|
||||||
|
{
|
||||||
|
void report(ubyte opcode, ref Expected expected, CpuInfo cpu,
|
||||||
|
const ref TestMemory mem, string msg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return &report;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Prints the differences between expected and actual cpu/memory.
|
||||||
|
auto report_debug()
|
||||||
|
{
|
||||||
|
void report(ubyte opcode, ref Expected expected, CpuInfo cpu,
|
||||||
|
const ref TestMemory mem, string msg)
|
||||||
|
{
|
||||||
|
import std.stdio;
|
||||||
|
bool badCpu = (expected.cpu != cpu);
|
||||||
|
bool badMem = (expected.mem != mem);
|
||||||
|
if (badCpu || badMem)
|
||||||
|
writeln(format("[%0.2X] %s", opcode, msg));
|
||||||
|
if (badCpu)
|
||||||
|
{
|
||||||
|
writeln(" expect ", expected.cpu);
|
||||||
|
writeln(" actual ", cpu);
|
||||||
|
}
|
||||||
|
if (badMem)
|
||||||
|
{
|
||||||
|
foreach (h; MemDiff(expected.mem, mem))
|
||||||
|
{
|
||||||
|
writeln(format(" %0.4X | %s", h.base, formatMemory(h.a, 8)));
|
||||||
|
writeln(format(" | %s", formatMemory(h.b, 8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (badCpu || badMem) throw new Exception("BAD");
|
||||||
|
}
|
||||||
|
return &report;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_one_opcode(T)(ubyte opcode, testreport report)
|
||||||
|
{
|
||||||
|
TestSetup setup_addr;
|
||||||
|
TestSetup setup_test;
|
||||||
|
testexpect expect;
|
||||||
|
|
||||||
|
mixin(getMemSetup!T());
|
||||||
|
|
||||||
|
auto setup = connect(setup_mask_flags(), setup_addr, setup_test);
|
||||||
|
auto run = connect(setup, run_opcode_test!T(expect, report));
|
||||||
|
run.run(opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
T[] If(alias cond, T)(T[] actions)
|
||||||
|
{
|
||||||
|
if (cond)
|
||||||
|
return actions;
|
||||||
|
else
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for register opcodes.
|
||||||
|
auto accesses_reg(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(REG_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
cycles = 2;
|
||||||
|
return [Bus(Action.READ, pc)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for push opcodes.
|
||||||
|
auto accesses_push(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(PUSH_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto sp = getSP(cpu);
|
||||||
|
|
||||||
|
cycles = 3;
|
||||||
|
return [Bus(Action.READ, pc)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+1)]) ~
|
||||||
|
[Bus(Action.WRITE, sp)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for pull opcodes.
|
||||||
|
auto accesses_pull(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(PULL_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto sp = getSP(cpu);
|
||||||
|
auto sp1 = pageWrapAdd(sp, 1);
|
||||||
|
|
||||||
|
cycles = 4;
|
||||||
|
return [Bus(Action.READ, pc)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, sp)]) ~
|
||||||
|
[Bus(Action.READ, sp1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for immediate mode opcodes.
|
||||||
|
auto accesses_imm(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(IMM_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
bool decimal = isCMOS!T && getFlag(cpu, Flag.D) &&
|
||||||
|
BCD_OPS!T.canFind(opcode);
|
||||||
|
|
||||||
|
cycles = 2 + decimal;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
If!decimal(If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+2)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for branch opcodes.
|
||||||
|
auto accesses_rel(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
||||||
|
assert(BRANCH_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto base = cast(ushort)(pc + 2);
|
||||||
|
bool branch = wouldBranch(cpu, opcode);
|
||||||
|
ushort wrongPage = pageWrapAdd(base, cast(byte)op1);
|
||||||
|
bool px = wrongPage != pageCrossAdd(base, cast(byte)op1);
|
||||||
|
ushort wrongAddr = isNMOS!T ? wrongPage : base;
|
||||||
|
|
||||||
|
cycles = 2 + branch + px;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
If!branch(If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+2)] ~
|
||||||
|
If!px(
|
||||||
|
[Bus(Action.READ, wrongAddr)])));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for zeropage mode opcodes.
|
||||||
|
auto accesses_zpg(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
||||||
|
assert(ZPG_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
cycles = 2; // + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
accesses_end(cpu, opcode, 2, op1, cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for absolute mode opcodes.
|
||||||
|
auto accesses_abs(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1], op2 = mem[pc+2];
|
||||||
|
assert(ABS_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto addr = address(op1, op2);
|
||||||
|
|
||||||
|
cycles = 3; // + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, pc+2)] ~
|
||||||
|
accesses_end(cpu, opcode, 3, addr, cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for zeropage,x/y mode opcodes.
|
||||||
|
auto accesses_zpxy(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
||||||
|
bool useX = ZPX_OPS!T.canFind(opcode);
|
||||||
|
assert(useX || ZPY_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto idx = (useX ? getX(cpu) : getY(cpu));
|
||||||
|
auto addr = pageWrapAdd(op1, idx);
|
||||||
|
|
||||||
|
cycles = 3; // + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
If!(isNMOS!T)(
|
||||||
|
[Bus(Action.READ, op1)]) ~
|
||||||
|
If!(isCMOS!T)(
|
||||||
|
[Bus(Action.READ, pc+2)])) ~ // XXX
|
||||||
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* According to "Understanding the Apple IIe", the extra read on
|
||||||
|
* the 65C02 (marked XXX above) is the address of the last operand
|
||||||
|
* byte (pc + 1).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for absolute,x/y mode opcodes.
|
||||||
|
auto accesses_abxy(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
auto op1 = mem[pc+1], op2 = mem[pc+2];
|
||||||
|
bool useX = ABX_OPS!T.canFind(opcode);
|
||||||
|
assert(useX || ABY_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
|
||||||
|
auto idx = useX ? getX(cpu) : getY(cpu);
|
||||||
|
auto base = address(op1, op2);
|
||||||
|
auto guess = pageWrapAdd(base, idx);
|
||||||
|
auto addr = pageCrossAdd(base, idx);
|
||||||
|
|
||||||
|
cycles = 3; // + accesses_px + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, pc+2)] ~
|
||||||
|
accesses_px(cpu, opcode, 3, guess, addr, cycles) ~
|
||||||
|
accesses_end(cpu, opcode, 3, addr, cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for indirect zeropage,x mode opcodes.
|
||||||
|
auto accesses_izx(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
||||||
|
assert(IZX_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto idx = getX(cpu);
|
||||||
|
auto ial = pageWrapAdd(op1, idx);
|
||||||
|
auto iah = pageWrapAdd(ial, 1);
|
||||||
|
auto addr = address(mem[ial], mem[iah]);
|
||||||
|
|
||||||
|
cycles = 5; // + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
If!(isNMOS!T)(
|
||||||
|
[Bus(Action.READ, op1)]) ~
|
||||||
|
If!(isCMOS!T)(
|
||||||
|
[Bus(Action.READ, pc+2)])) ~ // XXX
|
||||||
|
[Bus(Action.READ, ial),
|
||||||
|
Bus(Action.READ, iah)] ~
|
||||||
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* According to "Understanding the Apple IIe", the extra read on
|
||||||
|
* the 65C02 (marked XXX above) is the address of the last operand
|
||||||
|
* byte (pc + 1).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for indirect zeropage,y mode opcodes.
|
||||||
|
auto accesses_izy(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
||||||
|
assert(IZY_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto idx = getY(cpu);
|
||||||
|
auto ial = op1;
|
||||||
|
auto iah = pageWrapAdd(ial, 1);
|
||||||
|
auto base = address(mem[ial], mem[iah]);
|
||||||
|
auto guess = pageWrapAdd(base, idx);
|
||||||
|
auto addr = pageCrossAdd(base, idx);
|
||||||
|
|
||||||
|
cycles = 4; // + accesses_px + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, ial),
|
||||||
|
Bus(Action.READ, iah)] ~
|
||||||
|
accesses_px(cpu, opcode, 2, guess, addr, cycles) ~
|
||||||
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for indirect zeropage mode opcodes.
|
||||||
|
auto accesses_zpi(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T && isCMOS!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
||||||
|
assert(ZPI_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
auto ial = op1;
|
||||||
|
auto iah = pageWrapAdd(ial, 1);
|
||||||
|
auto addr = address(mem[ial], mem[iah]);
|
||||||
|
|
||||||
|
cycles = 4; // + accesses_end
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, ial),
|
||||||
|
Bus(Action.READ, iah)] ~
|
||||||
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for NMOS HLT opcodes.
|
||||||
|
auto accesses_hlt(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T && isNMOS!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(HLT_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
cycles = 1;
|
||||||
|
return [Bus(Action.READ, pc)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for 1-cycle NOPs.
|
||||||
|
auto accesses_nop1(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T && isCMOS!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(NOP1_OPS!T.canFind(opcode));
|
||||||
|
|
||||||
|
cycles = 1;
|
||||||
|
return [Bus(Action.READ, pc)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
auto accesses_px(T)(T cpu, ubyte opcode, int opLen, ushort guess, ushort right,
|
||||||
|
ref int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
bool noShortcut = WRITE_OPS!T.canFind(opcode) ||
|
||||||
|
(isNMOS!T ? (RMW_OPS!T.canFind(opcode))
|
||||||
|
: (opcode == 0xDE || opcode == 0xFE));
|
||||||
|
|
||||||
|
if (guess != right)
|
||||||
|
{
|
||||||
|
cycles += 1;
|
||||||
|
return If!(isStrict!T)(
|
||||||
|
If!(isNMOS!T)([Bus(Action.READ, guess)]) ~
|
||||||
|
If!(isCMOS!T)([Bus(Action.READ, pc + opLen)])); // XXX
|
||||||
|
}
|
||||||
|
else if (noShortcut)
|
||||||
|
{
|
||||||
|
cycles += 1;
|
||||||
|
return If!(isStrict!T)([Bus(Action.READ, guess)]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return cast(Bus[])[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* According to "Understanding the Apple IIe", the extra read on
|
||||||
|
* the 65C02 (marked XXX above) is the address of the last operand
|
||||||
|
* byte (pc + opLen - 1) for abx/aby, or the address of the high
|
||||||
|
* byte of the indirect address (op1 + 1) for izy.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
auto accesses_end(T)(T cpu, ubyte opcode, int opLen, ushort addr,
|
||||||
|
ref int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
bool rmw = RMW_OPS!T.canFind(opcode);
|
||||||
|
bool write = !rmw && WRITE_OPS!T.canFind(opcode);
|
||||||
|
bool read = !rmw && !write;
|
||||||
|
bool decimal = isCMOS!T && getFlag(cpu, Flag.D) &&
|
||||||
|
BCD_OPS!T.canFind(opcode);
|
||||||
|
|
||||||
|
cycles += (rmw ? 3 : (write ? 1 : (1 + decimal)));
|
||||||
|
return If!read(
|
||||||
|
[Bus(Action.READ, addr)] ~
|
||||||
|
If!decimal(If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc + opLen)]))) ~
|
||||||
|
If!write(
|
||||||
|
[Bus(Action.WRITE, addr)]) ~
|
||||||
|
If!rmw(
|
||||||
|
[Bus(Action.READ, addr)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
If!(isNMOS!T)(
|
||||||
|
[Bus(Action.WRITE, addr)]) ~
|
||||||
|
If!(isCMOS!T)(
|
||||||
|
[Bus(Action.READ, addr)])) ~
|
||||||
|
[Bus(Action.WRITE, addr)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for RTS.
|
||||||
|
auto accesses_op_RTS(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x60);
|
||||||
|
|
||||||
|
auto sp = getSP(cpu);
|
||||||
|
auto sp1 = pageWrapAdd(sp, 1);
|
||||||
|
auto sp2 = pageWrapAdd(sp, 2);
|
||||||
|
auto ret = address(mem[sp1], mem[sp2]);
|
||||||
|
|
||||||
|
cycles = 6;
|
||||||
|
return [Bus(Action.READ, pc)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, sp)]) ~
|
||||||
|
[Bus(Action.READ, sp1),
|
||||||
|
Bus(Action.READ, sp2)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, ret)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for RTI.
|
||||||
|
auto accesses_op_RTI(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x40);
|
||||||
|
|
||||||
|
auto sp = getSP(cpu);
|
||||||
|
auto sp1 = pageWrapAdd(sp, 1);
|
||||||
|
auto sp2 = pageWrapAdd(sp, 2);
|
||||||
|
auto sp3 = pageWrapAdd(sp, 3);
|
||||||
|
|
||||||
|
cycles = 6;
|
||||||
|
return [Bus(Action.READ, pc)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, sp)]) ~
|
||||||
|
[Bus(Action.READ, sp1),
|
||||||
|
Bus(Action.READ, sp2),
|
||||||
|
Bus(Action.READ, sp3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for BRK.
|
||||||
|
auto accesses_op_BRK(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x00);
|
||||||
|
|
||||||
|
auto sp = getSP(cpu);
|
||||||
|
auto sp1 = pageWrapAdd(sp, -1);
|
||||||
|
auto sp2 = pageWrapAdd(sp, -2);
|
||||||
|
|
||||||
|
cycles = 7;
|
||||||
|
return [Bus(Action.READ, pc)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+1)]) ~
|
||||||
|
[Bus(Action.WRITE, sp),
|
||||||
|
Bus(Action.WRITE, sp1),
|
||||||
|
Bus(Action.WRITE, sp2),
|
||||||
|
Bus(Action.READ, 0xFFFE),
|
||||||
|
Bus(Action.READ, 0xFFFF)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for JSR
|
||||||
|
auto accesses_op_JSR(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x20);
|
||||||
|
|
||||||
|
auto sp = getSP(cpu);
|
||||||
|
auto sp1 = pageWrapAdd(sp, -1);
|
||||||
|
|
||||||
|
cycles = 6;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, sp)]) ~
|
||||||
|
[Bus(Action.WRITE, sp),
|
||||||
|
Bus(Action.WRITE, sp1),
|
||||||
|
Bus(Action.READ, pc+2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for JMP absolute
|
||||||
|
auto accesses_op_JMP_abs(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x4C);
|
||||||
|
|
||||||
|
cycles = 3;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, pc+2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for JMP indirect
|
||||||
|
auto accesses_op_JMP_ind(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc], op1 = mem[pc+1], op2 = mem[pc+2];
|
||||||
|
assert(opcode == 0x6C);
|
||||||
|
|
||||||
|
auto ial = address(op1, op2);
|
||||||
|
auto iah = (isNMOS!T ? pageWrapAdd(ial, 1)
|
||||||
|
: pageCrossAdd(ial, 1));
|
||||||
|
|
||||||
|
cycles = 5 + isCMOS!T;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, pc+2)] ~
|
||||||
|
If!(isStrict!T)(If!(isCMOS!T)(
|
||||||
|
[Bus(Action.READ, pc+3)])) ~ // XXX
|
||||||
|
[Bus(Action.READ, ial),
|
||||||
|
Bus(Action.READ, iah)];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* According to "Understanding the Apple IIe", the extra read on
|
||||||
|
* the 65C02 (marked XXX above) is the address of the last operand
|
||||||
|
* byte (pc + 2).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for JMP indirect,x
|
||||||
|
auto accesses_op_JMP_inx(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T && isCMOS!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x7C);
|
||||||
|
|
||||||
|
auto idx = getX(cpu);
|
||||||
|
auto base = address(mem[pc+1], mem[pc+2]);
|
||||||
|
auto ial = pageCrossAdd(base, idx);
|
||||||
|
auto iah = pageCrossAdd(ial, 1);
|
||||||
|
|
||||||
|
cycles = 6;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1),
|
||||||
|
Bus(Action.READ, pc+2)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+3)]) ~ // XXX
|
||||||
|
[Bus(Action.READ, ial),
|
||||||
|
Bus(Action.READ, iah)];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* According to "Understanding the Apple IIe", the extra read on
|
||||||
|
* the 65C02 (marked XXX above) is the address of the last operand
|
||||||
|
* byte (pc + 2).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Bus access pattern for CMOS opcode 5C
|
||||||
|
auto accesses_op_5C(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||||
|
if (isCpu!T && isCMOS!T)
|
||||||
|
{
|
||||||
|
auto pc = getPC(cpu);
|
||||||
|
auto opcode = mem[pc];
|
||||||
|
assert(opcode == 0x5C);
|
||||||
|
|
||||||
|
auto weird = address(mem[pc+1], 0xFF);
|
||||||
|
|
||||||
|
cycles = 8;
|
||||||
|
return [Bus(Action.READ, pc),
|
||||||
|
Bus(Action.READ, pc+1)] ~
|
||||||
|
If!(isStrict!T)(
|
||||||
|
[Bus(Action.READ, pc+2),
|
||||||
|
Bus(Action.READ, weird),
|
||||||
|
Bus(Action.READ, 0xFFFF),
|
||||||
|
Bus(Action.READ, 0xFFFF),
|
||||||
|
Bus(Action.READ, 0xFFFF),
|
||||||
|
Bus(Action.READ, 0xFFFF)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Associates opcodes with expected access patterns.
|
||||||
|
string getExpected(T)()
|
||||||
|
{
|
||||||
|
string[] tmp = new string[256];
|
||||||
|
|
||||||
|
void add_op(const(ubyte[]) list, string fname)
|
||||||
|
{
|
||||||
|
foreach(op; list)
|
||||||
|
{
|
||||||
|
tmp[op] = " case 0x" ~ to!string(op, 16) ~ ": " ~
|
||||||
|
"expected = &" ~ fname ~ "!T; break;";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_op(REG_OPS!T, "accesses_reg");
|
||||||
|
add_op(PUSH_OPS!T, "accesses_push");
|
||||||
|
add_op(PULL_OPS!T, "accesses_pull");
|
||||||
|
add_op(BRANCH_OPS!T, "accesses_rel");
|
||||||
|
add_op(IMM_OPS!T, "accesses_imm");
|
||||||
|
add_op(ZPG_OPS!T, "accesses_zpg");
|
||||||
|
add_op(ZPX_OPS!T, "accesses_zpxy");
|
||||||
|
add_op(ZPY_OPS!T, "accesses_zpxy");
|
||||||
|
add_op(ABS_OPS!T, "accesses_abs");
|
||||||
|
add_op(ABX_OPS!T, "accesses_abxy");
|
||||||
|
add_op(ABY_OPS!T, "accesses_abxy");
|
||||||
|
add_op(IZX_OPS!T, "accesses_izx");
|
||||||
|
add_op(IZY_OPS!T, "accesses_izy");
|
||||||
|
add_op([0x00], "accesses_op_BRK");
|
||||||
|
add_op([0x20], "accesses_op_JSR");
|
||||||
|
add_op([0x40], "accesses_op_RTI");
|
||||||
|
add_op([0x4C], "accesses_op_JMP_abs");
|
||||||
|
add_op([0x60], "accesses_op_RTS");
|
||||||
|
add_op([0x6C], "accesses_op_JMP_ind");
|
||||||
|
static if (isNMOS!T)
|
||||||
|
add_op(HLT_OPS!T, "accesses_hlt");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add_op(ZPI_OPS!T, "accesses_zpi");
|
||||||
|
add_op(NOP1_OPS!T, "accesses_nop1");
|
||||||
|
add_op([0x7C], "accesses_op_JMP_inx");
|
||||||
|
add_op([0x5C], "accesses_op_5C");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "final switch (opcode)\n{\n" ~ join(tmp, "\n") ~ "\n}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template timesetup_t(T)
|
||||||
|
{
|
||||||
|
alias Bus[] function(T, ref TestMemory, out int) timesetup_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
alias void delegate(int, const Bus[], int, const Bus[], ubyte, string)
|
||||||
|
busreport;
|
||||||
|
|
||||||
|
|
||||||
|
auto run_timing_test(T)(timesetup_t!T expect, 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);
|
||||||
|
|
||||||
|
int expCycles;
|
||||||
|
auto expBus = expect(testcpu, mem, expCycles);
|
||||||
|
expBus = expBus ~ new Bus[8 - expBus.length];
|
||||||
|
|
||||||
|
connectMem(testcpu, mem);
|
||||||
|
auto actualBus = recordBus(testcpu);
|
||||||
|
auto actualCycles = recordCycles(testcpu);
|
||||||
|
|
||||||
|
runOneOpcode(testcpu);
|
||||||
|
|
||||||
|
report(actualCycles, actualBus, expCycles, expBus,
|
||||||
|
opcode, T.stringof ~ " | " ~ msg);
|
||||||
|
callNext();
|
||||||
|
}
|
||||||
|
return TestSetup(&setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
auto report_timing_debug()
|
||||||
|
{
|
||||||
|
void report(int actualCycles, const Bus[] actualBus,
|
||||||
|
int expectCycles, const Bus[] expectBus,
|
||||||
|
ubyte opcode, string msg)
|
||||||
|
{
|
||||||
|
if (actualBus != expectBus)
|
||||||
|
{
|
||||||
|
// XXX make error message, throw
|
||||||
|
}
|
||||||
|
if (actualCycles != expectCycles)
|
||||||
|
{
|
||||||
|
// XXX make error message, throw
|
||||||
|
}
|
||||||
|
if (actualBus == expectBus && actualCycles == expectCycles) {}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write(format("[%0.2X] %s", opcode, msg));
|
||||||
|
writeln();
|
||||||
|
writeln(expectCycles, " ", actualCycles);
|
||||||
|
writeln(expectBus);
|
||||||
|
writeln(actualBus);
|
||||||
|
throw new TestException("timing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &report;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Tests the bus access patterns and cycles taken for a given opcode.
|
||||||
|
void test_opcode_timing(T)(ubyte opcode, busreport report)
|
||||||
|
{
|
||||||
|
TestSetup setup_addr;
|
||||||
|
TestSetup setup_test;
|
||||||
|
testexpect expect;
|
||||||
|
timesetup_t!T expected;
|
||||||
|
|
||||||
|
mixin(getMemSetup!T());
|
||||||
|
mixin(getExpected!T());
|
||||||
|
|
||||||
|
auto setup = connect(setup_mask_flags(), setup_addr, setup_test);
|
||||||
|
auto run = connect(setup, run_timing_test!T(expected, report));
|
||||||
|
run.run(opcode);
|
||||||
|
}
|
||||||
|
|
690
test/test_bus.d
690
test/test_bus.d
|
@ -1,696 +1,10 @@
|
||||||
module test.test_bus;
|
module test.test_bus;
|
||||||
|
|
||||||
|
|
||||||
import std.algorithm, std.array, std.conv, std.exception, std.stdio,
|
import test.base, test.cpu;
|
||||||
std.string;
|
|
||||||
|
|
||||||
import test.base, test.cpu, test.opcodes;
|
|
||||||
|
|
||||||
|
void main()
|
||||||
T[] If(alias cond, T)(T[] actions)
|
|
||||||
{
|
|
||||||
if (cond)
|
|
||||||
return actions;
|
|
||||||
else
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for register opcodes.
|
|
||||||
auto accesses_reg(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(REG_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
cycles = 2;
|
|
||||||
return [Bus(Action.READ, pc)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+1)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for push opcodes.
|
|
||||||
auto accesses_push(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(PUSH_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto sp = getSP(cpu);
|
|
||||||
|
|
||||||
cycles = 3;
|
|
||||||
return [Bus(Action.READ, pc)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+1)]) ~
|
|
||||||
[Bus(Action.WRITE, sp)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for pull opcodes.
|
|
||||||
auto accesses_pull(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(PULL_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto sp = getSP(cpu);
|
|
||||||
auto sp1 = pageWrapAdd(sp, 1);
|
|
||||||
|
|
||||||
cycles = 4;
|
|
||||||
return [Bus(Action.READ, pc)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, sp)]) ~
|
|
||||||
[Bus(Action.READ, sp1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for immediate mode opcodes.
|
|
||||||
auto accesses_imm(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(IMM_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
bool decimal = isCMOS!T && getFlag(cpu, Flag.D) &&
|
|
||||||
BCD_OPS!T.canFind(opcode);
|
|
||||||
|
|
||||||
cycles = 2 + decimal;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
If!decimal(If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+2)]));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for branch opcodes.
|
|
||||||
auto accesses_rel(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
||||||
assert(BRANCH_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto base = cast(ushort)(pc + 2);
|
|
||||||
bool branch = wouldBranch(cpu, opcode);
|
|
||||||
ushort wrongPage = pageWrapAdd(base, cast(byte)op1);
|
|
||||||
bool px = wrongPage != pageCrossAdd(base, cast(byte)op1);
|
|
||||||
ushort wrongAddr = isNMOS!T ? wrongPage : base;
|
|
||||||
|
|
||||||
cycles = 2 + branch + px;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
If!branch(If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+2)] ~
|
|
||||||
If!px(
|
|
||||||
[Bus(Action.READ, wrongAddr)])));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for zeropage mode opcodes.
|
|
||||||
auto accesses_zpg(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
||||||
assert(ZPG_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
cycles = 2; // + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
accesses_end(cpu, opcode, 2, op1, cycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for absolute mode opcodes.
|
|
||||||
auto accesses_abs(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1], op2 = mem[pc+2];
|
|
||||||
assert(ABS_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto addr = address(op1, op2);
|
|
||||||
|
|
||||||
cycles = 3; // + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, pc+2)] ~
|
|
||||||
accesses_end(cpu, opcode, 3, addr, cycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for zeropage,x/y mode opcodes.
|
|
||||||
auto accesses_zpxy(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
||||||
bool useX = ZPX_OPS!T.canFind(opcode);
|
|
||||||
assert(useX || ZPY_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto idx = (useX ? getX(cpu) : getY(cpu));
|
|
||||||
auto addr = pageWrapAdd(op1, idx);
|
|
||||||
|
|
||||||
cycles = 3; // + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
If!(isNMOS!T)(
|
|
||||||
[Bus(Action.READ, op1)]) ~
|
|
||||||
If!(isCMOS!T)(
|
|
||||||
[Bus(Action.READ, pc+2)])) ~ // XXX
|
|
||||||
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* According to "Understanding the Apple IIe", the extra read on
|
|
||||||
* the 65C02 (marked XXX above) is the address of the last operand
|
|
||||||
* byte (pc + 1).
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for absolute,x/y mode opcodes.
|
|
||||||
auto accesses_abxy(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
auto op1 = mem[pc+1], op2 = mem[pc+2];
|
|
||||||
bool useX = ABX_OPS!T.canFind(opcode);
|
|
||||||
assert(useX || ABY_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
|
|
||||||
auto idx = useX ? getX(cpu) : getY(cpu);
|
|
||||||
auto base = address(op1, op2);
|
|
||||||
auto guess = pageWrapAdd(base, idx);
|
|
||||||
auto addr = pageCrossAdd(base, idx);
|
|
||||||
|
|
||||||
cycles = 3; // + accesses_px + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, pc+2)] ~
|
|
||||||
accesses_px(cpu, opcode, 3, guess, addr, cycles) ~
|
|
||||||
accesses_end(cpu, opcode, 3, addr, cycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for indirect zeropage,x mode opcodes.
|
|
||||||
auto accesses_izx(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
||||||
assert(IZX_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto idx = getX(cpu);
|
|
||||||
auto ial = pageWrapAdd(op1, idx);
|
|
||||||
auto iah = pageWrapAdd(ial, 1);
|
|
||||||
auto addr = address(mem[ial], mem[iah]);
|
|
||||||
|
|
||||||
cycles = 5; // + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
If!(isNMOS!T)(
|
|
||||||
[Bus(Action.READ, op1)]) ~
|
|
||||||
If!(isCMOS!T)(
|
|
||||||
[Bus(Action.READ, pc+2)])) ~ // XXX
|
|
||||||
[Bus(Action.READ, ial),
|
|
||||||
Bus(Action.READ, iah)] ~
|
|
||||||
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* According to "Understanding the Apple IIe", the extra read on
|
|
||||||
* the 65C02 (marked XXX above) is the address of the last operand
|
|
||||||
* byte (pc + 1).
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for indirect zeropage,y mode opcodes.
|
|
||||||
auto accesses_izy(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
||||||
assert(IZY_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto idx = getY(cpu);
|
|
||||||
auto ial = op1;
|
|
||||||
auto iah = pageWrapAdd(ial, 1);
|
|
||||||
auto base = address(mem[ial], mem[iah]);
|
|
||||||
auto guess = pageWrapAdd(base, idx);
|
|
||||||
auto addr = pageCrossAdd(base, idx);
|
|
||||||
|
|
||||||
cycles = 4; // + accesses_px + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, ial),
|
|
||||||
Bus(Action.READ, iah)] ~
|
|
||||||
accesses_px(cpu, opcode, 2, guess, addr, cycles) ~
|
|
||||||
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for indirect zeropage mode opcodes.
|
|
||||||
auto accesses_zpi(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T && isCMOS!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
||||||
assert(ZPI_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
auto ial = op1;
|
|
||||||
auto iah = pageWrapAdd(ial, 1);
|
|
||||||
auto addr = address(mem[ial], mem[iah]);
|
|
||||||
|
|
||||||
cycles = 4; // + accesses_end
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, ial),
|
|
||||||
Bus(Action.READ, iah)] ~
|
|
||||||
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for NMOS HLT opcodes.
|
|
||||||
auto accesses_hlt(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T && isNMOS!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(HLT_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
cycles = 1;
|
|
||||||
return [Bus(Action.READ, pc)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for 1-cycle NOPs.
|
|
||||||
auto accesses_nop1(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T && isCMOS!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(NOP1_OPS!T.canFind(opcode));
|
|
||||||
|
|
||||||
cycles = 1;
|
|
||||||
return [Bus(Action.READ, pc)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
auto accesses_px(T)(T cpu, ubyte opcode, int opLen, ushort guess, ushort right,
|
|
||||||
ref int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
bool noShortcut = WRITE_OPS!T.canFind(opcode) ||
|
|
||||||
(isNMOS!T ? (RMW_OPS!T.canFind(opcode))
|
|
||||||
: (opcode == 0xDE || opcode == 0xFE));
|
|
||||||
|
|
||||||
if (guess != right)
|
|
||||||
{
|
|
||||||
cycles += 1;
|
|
||||||
return If!(isStrict!T)(
|
|
||||||
If!(isNMOS!T)([Bus(Action.READ, guess)]) ~
|
|
||||||
If!(isCMOS!T)([Bus(Action.READ, pc + opLen)])); // XXX
|
|
||||||
}
|
|
||||||
else if (noShortcut)
|
|
||||||
{
|
|
||||||
cycles += 1;
|
|
||||||
return If!(isStrict!T)([Bus(Action.READ, guess)]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return cast(Bus[])[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* According to "Understanding the Apple IIe", the extra read on
|
|
||||||
* the 65C02 (marked XXX above) is the address of the last operand
|
|
||||||
* byte (pc + opLen - 1) for abx/aby, or the address of the high
|
|
||||||
* byte of the indirect address (op1 + 1) for izy.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
auto accesses_end(T)(T cpu, ubyte opcode, int opLen, ushort addr,
|
|
||||||
ref int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
bool rmw = RMW_OPS!T.canFind(opcode);
|
|
||||||
bool write = !rmw && WRITE_OPS!T.canFind(opcode);
|
|
||||||
bool read = !rmw && !write;
|
|
||||||
bool decimal = isCMOS!T && getFlag(cpu, Flag.D) &&
|
|
||||||
BCD_OPS!T.canFind(opcode);
|
|
||||||
|
|
||||||
cycles += (rmw ? 3 : (write ? 1 : (1 + decimal)));
|
|
||||||
return If!read(
|
|
||||||
[Bus(Action.READ, addr)] ~
|
|
||||||
If!decimal(If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc + opLen)]))) ~
|
|
||||||
If!write(
|
|
||||||
[Bus(Action.WRITE, addr)]) ~
|
|
||||||
If!rmw(
|
|
||||||
[Bus(Action.READ, addr)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
If!(isNMOS!T)(
|
|
||||||
[Bus(Action.WRITE, addr)]) ~
|
|
||||||
If!(isCMOS!T)(
|
|
||||||
[Bus(Action.READ, addr)])) ~
|
|
||||||
[Bus(Action.WRITE, addr)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for RTS.
|
|
||||||
auto accesses_op_RTS(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x60);
|
|
||||||
|
|
||||||
auto sp = getSP(cpu);
|
|
||||||
auto sp1 = pageWrapAdd(sp, 1);
|
|
||||||
auto sp2 = pageWrapAdd(sp, 2);
|
|
||||||
auto ret = address(mem[sp1], mem[sp2]);
|
|
||||||
|
|
||||||
cycles = 6;
|
|
||||||
return [Bus(Action.READ, pc)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, sp)]) ~
|
|
||||||
[Bus(Action.READ, sp1),
|
|
||||||
Bus(Action.READ, sp2)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, ret)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for RTI.
|
|
||||||
auto accesses_op_RTI(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x40);
|
|
||||||
|
|
||||||
auto sp = getSP(cpu);
|
|
||||||
auto sp1 = pageWrapAdd(sp, 1);
|
|
||||||
auto sp2 = pageWrapAdd(sp, 2);
|
|
||||||
auto sp3 = pageWrapAdd(sp, 3);
|
|
||||||
|
|
||||||
cycles = 6;
|
|
||||||
return [Bus(Action.READ, pc)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, sp)]) ~
|
|
||||||
[Bus(Action.READ, sp1),
|
|
||||||
Bus(Action.READ, sp2),
|
|
||||||
Bus(Action.READ, sp3)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for BRK.
|
|
||||||
auto accesses_op_BRK(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x00);
|
|
||||||
|
|
||||||
auto sp = getSP(cpu);
|
|
||||||
auto sp1 = pageWrapAdd(sp, -1);
|
|
||||||
auto sp2 = pageWrapAdd(sp, -2);
|
|
||||||
|
|
||||||
cycles = 7;
|
|
||||||
return [Bus(Action.READ, pc)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+1)]) ~
|
|
||||||
[Bus(Action.WRITE, sp),
|
|
||||||
Bus(Action.WRITE, sp1),
|
|
||||||
Bus(Action.WRITE, sp2),
|
|
||||||
Bus(Action.READ, 0xFFFE),
|
|
||||||
Bus(Action.READ, 0xFFFF)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for JSR
|
|
||||||
auto accesses_op_JSR(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x20);
|
|
||||||
|
|
||||||
auto sp = getSP(cpu);
|
|
||||||
auto sp1 = pageWrapAdd(sp, -1);
|
|
||||||
|
|
||||||
cycles = 6;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, sp)]) ~
|
|
||||||
[Bus(Action.WRITE, sp),
|
|
||||||
Bus(Action.WRITE, sp1),
|
|
||||||
Bus(Action.READ, pc+2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for JMP absolute
|
|
||||||
auto accesses_op_JMP_abs(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x4C);
|
|
||||||
|
|
||||||
cycles = 3;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, pc+2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for JMP indirect
|
|
||||||
auto accesses_op_JMP_ind(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc], op1 = mem[pc+1], op2 = mem[pc+2];
|
|
||||||
assert(opcode == 0x6C);
|
|
||||||
|
|
||||||
auto ial = address(op1, op2);
|
|
||||||
auto iah = (isNMOS!T ? pageWrapAdd(ial, 1)
|
|
||||||
: pageCrossAdd(ial, 1));
|
|
||||||
|
|
||||||
cycles = 5 + isCMOS!T;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, pc+2)] ~
|
|
||||||
If!(isStrict!T)(If!(isCMOS!T)(
|
|
||||||
[Bus(Action.READ, pc+3)])) ~ // XXX
|
|
||||||
[Bus(Action.READ, ial),
|
|
||||||
Bus(Action.READ, iah)];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* According to "Understanding the Apple IIe", the extra read on
|
|
||||||
* the 65C02 (marked XXX above) is the address of the last operand
|
|
||||||
* byte (pc + 2).
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for JMP indirect,x
|
|
||||||
auto accesses_op_JMP_inx(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T && isCMOS!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x7C);
|
|
||||||
|
|
||||||
auto idx = getX(cpu);
|
|
||||||
auto base = address(mem[pc+1], mem[pc+2]);
|
|
||||||
auto ial = pageCrossAdd(base, idx);
|
|
||||||
auto iah = pageCrossAdd(ial, 1);
|
|
||||||
|
|
||||||
cycles = 6;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1),
|
|
||||||
Bus(Action.READ, pc+2)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+3)]) ~ // XXX
|
|
||||||
[Bus(Action.READ, ial),
|
|
||||||
Bus(Action.READ, iah)];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* According to "Understanding the Apple IIe", the extra read on
|
|
||||||
* the 65C02 (marked XXX above) is the address of the last operand
|
|
||||||
* byte (pc + 2).
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Bus access pattern for CMOS opcode 5C
|
|
||||||
auto accesses_op_5C(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
||||||
if (isCpu!T && isCMOS!T)
|
|
||||||
{
|
|
||||||
auto pc = getPC(cpu);
|
|
||||||
auto opcode = mem[pc];
|
|
||||||
assert(opcode == 0x5C);
|
|
||||||
|
|
||||||
auto weird = address(mem[pc+1], 0xFF);
|
|
||||||
|
|
||||||
cycles = 8;
|
|
||||||
return [Bus(Action.READ, pc),
|
|
||||||
Bus(Action.READ, pc+1)] ~
|
|
||||||
If!(isStrict!T)(
|
|
||||||
[Bus(Action.READ, pc+2),
|
|
||||||
Bus(Action.READ, weird),
|
|
||||||
Bus(Action.READ, 0xFFFF),
|
|
||||||
Bus(Action.READ, 0xFFFF),
|
|
||||||
Bus(Action.READ, 0xFFFF),
|
|
||||||
Bus(Action.READ, 0xFFFF)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Associates opcodes with expected access patterns.
|
|
||||||
string getExpected(T)()
|
|
||||||
{
|
|
||||||
string[] tmp = new string[256];
|
|
||||||
|
|
||||||
void add_op(const(ubyte[]) list, string fname)
|
|
||||||
{
|
|
||||||
foreach(op; list)
|
|
||||||
{
|
|
||||||
tmp[op] = " case 0x" ~ to!string(op, 16) ~ ": " ~
|
|
||||||
"expected = &" ~ fname ~ "!T; break;";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_op(REG_OPS!T, "accesses_reg");
|
|
||||||
add_op(PUSH_OPS!T, "accesses_push");
|
|
||||||
add_op(PULL_OPS!T, "accesses_pull");
|
|
||||||
add_op(BRANCH_OPS!T, "accesses_rel");
|
|
||||||
add_op(IMM_OPS!T, "accesses_imm");
|
|
||||||
add_op(ZPG_OPS!T, "accesses_zpg");
|
|
||||||
add_op(ZPX_OPS!T, "accesses_zpxy");
|
|
||||||
add_op(ZPY_OPS!T, "accesses_zpxy");
|
|
||||||
add_op(ABS_OPS!T, "accesses_abs");
|
|
||||||
add_op(ABX_OPS!T, "accesses_abxy");
|
|
||||||
add_op(ABY_OPS!T, "accesses_abxy");
|
|
||||||
add_op(IZX_OPS!T, "accesses_izx");
|
|
||||||
add_op(IZY_OPS!T, "accesses_izy");
|
|
||||||
add_op([0x00], "accesses_op_BRK");
|
|
||||||
add_op([0x20], "accesses_op_JSR");
|
|
||||||
add_op([0x40], "accesses_op_RTI");
|
|
||||||
add_op([0x4C], "accesses_op_JMP_abs");
|
|
||||||
add_op([0x60], "accesses_op_RTS");
|
|
||||||
add_op([0x6C], "accesses_op_JMP_ind");
|
|
||||||
static if (isNMOS!T)
|
|
||||||
add_op(HLT_OPS!T, "accesses_hlt");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
add_op(ZPI_OPS!T, "accesses_zpi");
|
|
||||||
add_op(NOP1_OPS!T, "accesses_nop1");
|
|
||||||
add_op([0x7C], "accesses_op_JMP_inx");
|
|
||||||
add_op([0x5C], "accesses_op_5C");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "final switch (opcode)\n{\n" ~ join(tmp, "\n") ~ "\n}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template timesetup_t(T)
|
|
||||||
{
|
|
||||||
alias Bus[] function(T, ref TestMemory, out int) timesetup_t;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
alias void delegate(int, const Bus[], int, const Bus[], ubyte, string)
|
|
||||||
busreport;
|
|
||||||
|
|
||||||
|
|
||||||
auto run_timing_test(T)(timesetup_t!T expect, 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);
|
|
||||||
|
|
||||||
int expCycles;
|
|
||||||
auto expBus = expect(testcpu, mem, expCycles);
|
|
||||||
expBus = expBus ~ new Bus[8 - expBus.length];
|
|
||||||
|
|
||||||
connectMem(testcpu, mem);
|
|
||||||
auto actualBus = recordBus(testcpu);
|
|
||||||
auto actualCycles = recordCycles(testcpu);
|
|
||||||
|
|
||||||
runOneOpcode(testcpu);
|
|
||||||
|
|
||||||
report(actualCycles, actualBus, expCycles, expBus,
|
|
||||||
opcode, T.stringof ~ " | " ~ msg);
|
|
||||||
callNext();
|
|
||||||
}
|
|
||||||
return TestSetup(&setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
auto report_timing_debug()
|
|
||||||
{
|
|
||||||
void report(int actualCycles, const Bus[] actualBus,
|
|
||||||
int expectCycles, const Bus[] expectBus,
|
|
||||||
ubyte opcode, string msg)
|
|
||||||
{
|
|
||||||
if (actualBus != expectBus)
|
|
||||||
{
|
|
||||||
// XXX make error message, throw
|
|
||||||
}
|
|
||||||
if (actualCycles != expectCycles)
|
|
||||||
{
|
|
||||||
// XXX make error message, throw
|
|
||||||
}
|
|
||||||
if (actualBus == expectBus && actualCycles == expectCycles) {}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write(format("[%0.2X] %s", opcode, msg));
|
|
||||||
writeln();
|
|
||||||
writeln(expectCycles, " ", actualCycles);
|
|
||||||
writeln(expectBus);
|
|
||||||
writeln(actualBus);
|
|
||||||
throw new TestException("timing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &report;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Tests the bus access patterns and cycles taken for a given opcode.
|
|
||||||
void test_opcode_timing(T)(ubyte opcode, busreport report)
|
|
||||||
{
|
|
||||||
TestSetup setup_addr;
|
|
||||||
TestSetup setup_test;
|
|
||||||
testexpect expect;
|
|
||||||
timesetup_t!T expected;
|
|
||||||
|
|
||||||
mixin(getMemSetup!T());
|
|
||||||
mixin(getExpected!T());
|
|
||||||
|
|
||||||
auto setup = connect(setup_mask_flags(), setup_addr, setup_test);
|
|
||||||
auto run = connect(setup, run_timing_test!T(expected, report));
|
|
||||||
run.run(opcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest
|
|
||||||
{
|
{
|
||||||
auto report = report_timing_debug();
|
auto report = report_timing_debug();
|
||||||
|
|
||||||
|
|
4
test/test_cpu_all.sh
Executable file
4
test/test_cpu_all.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
rdmd @testopts test_func.d
|
||||||
|
rdmd @testopts test_bus.d
|
||||||
|
rdmd @testopts test_decimal.d
|
||||||
|
|
|
@ -226,7 +226,25 @@ if (isCpu!T)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unittest
|
version(Benchmark)
|
||||||
|
{
|
||||||
|
import std.datetime, std.stdio;
|
||||||
|
void f0()
|
||||||
|
{
|
||||||
|
testDecimalMode!(CPU!("65C02", false, false))();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// auto milliExpected = (61886766.0 / 1020484.0) * 1000;
|
||||||
|
auto milliExpected = (64508206.0 / 1020484.0) * 1000;
|
||||||
|
auto r = benchmark!(f0)(1);
|
||||||
|
writeln(milliExpected / r[0].to!("msecs", int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
void main()
|
||||||
{
|
{
|
||||||
writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.no)");
|
writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.no)");
|
||||||
testDecimalMode!(CPU!("6502", false, false))();
|
testDecimalMode!(CPU!("6502", false, false))();
|
||||||
|
@ -248,21 +266,4 @@ unittest
|
||||||
writeln("Testing decimal mode, CMOS(Strict.yes, Cumulative.yes)");
|
writeln("Testing decimal mode, CMOS(Strict.yes, Cumulative.yes)");
|
||||||
testDecimalMode!(CPU!("65C02", true, true))();
|
testDecimalMode!(CPU!("65C02", true, true))();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
version(Benchmark)
|
|
||||||
{
|
|
||||||
import std.datetime, std.stdio;
|
|
||||||
void f0()
|
|
||||||
{
|
|
||||||
testDecimalMode!(CPU!("65C02", false, false))();
|
|
||||||
}
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
// auto milliExpected = (61886766.0 / 1020484.0) * 1000;
|
|
||||||
auto milliExpected = (64508206.0 / 1020484.0) * 1000;
|
|
||||||
auto r = benchmark!(f0)(1);
|
|
||||||
writeln(milliExpected / r[0].to!("msecs", int));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,96 +1,10 @@
|
||||||
module test.test_func;
|
module test.test_func;
|
||||||
|
|
||||||
|
|
||||||
import std.string;
|
import test.base, test.cpu;
|
||||||
|
|
||||||
import test.base, test.cpu, test.opcodes;
|
|
||||||
|
|
||||||
|
|
||||||
alias void delegate(ubyte, ref Expected, CpuInfo, const ref TestMemory, string)
|
void main()
|
||||||
testreport;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Runs one opcode. Calls expect for the expected values of the cpu
|
|
||||||
* registers and memory. Calls report with the expected and actual
|
|
||||||
* values.
|
|
||||||
*/
|
|
||||||
auto run_opcode_test(T)(testexpect expect, 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(expected, info);
|
|
||||||
connectMem(testcpu, mem);
|
|
||||||
runOneOpcode(testcpu);
|
|
||||||
auto cpuResult = CpuInfo.fromCpu(testcpu);
|
|
||||||
report(opcode, expected, cpuResult, mem, T.stringof ~ " | " ~ msg);
|
|
||||||
callNext();
|
|
||||||
}
|
|
||||||
return TestSetup(&setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Dummy function. Reports nothing.
|
|
||||||
auto report_none()
|
|
||||||
{
|
|
||||||
void report(ubyte opcode, ref Expected expected, CpuInfo cpu,
|
|
||||||
const ref TestMemory mem, string msg)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
return &report;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Prints the differences between expected and actual cpu/memory.
|
|
||||||
auto report_debug()
|
|
||||||
{
|
|
||||||
void report(ubyte opcode, ref Expected expected, CpuInfo cpu,
|
|
||||||
const ref TestMemory mem, string msg)
|
|
||||||
{
|
|
||||||
import std.stdio;
|
|
||||||
bool badCpu = (expected.cpu != cpu);
|
|
||||||
bool badMem = (expected.mem != mem);
|
|
||||||
if (badCpu || badMem)
|
|
||||||
writeln(format("[%0.2X] %s", opcode, msg));
|
|
||||||
if (badCpu)
|
|
||||||
{
|
|
||||||
writeln(" expect ", expected.cpu);
|
|
||||||
writeln(" actual ", cpu);
|
|
||||||
}
|
|
||||||
if (badMem)
|
|
||||||
{
|
|
||||||
foreach (h; MemDiff(expected.mem, mem))
|
|
||||||
{
|
|
||||||
writeln(format(" %0.4X | %s", h.base, formatMemory(h.a, 8)));
|
|
||||||
writeln(format(" | %s", formatMemory(h.b, 8)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (badCpu || badMem) throw new Exception("BAD");
|
|
||||||
}
|
|
||||||
return &report;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void test_one_opcode(T)(ubyte opcode, testreport report)
|
|
||||||
{
|
|
||||||
TestSetup setup_addr;
|
|
||||||
TestSetup setup_test;
|
|
||||||
testexpect expect;
|
|
||||||
|
|
||||||
mixin(getMemSetup!T());
|
|
||||||
|
|
||||||
auto setup = connect(setup_mask_flags(), setup_addr, setup_test);
|
|
||||||
auto run = connect(setup, run_opcode_test!T(expect, report));
|
|
||||||
run.run(opcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
unittest
|
|
||||||
{
|
{
|
||||||
auto report = report_debug();
|
auto report = report_debug();
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
-unittest -I.. -I../src -g
|
-I.. -I../src -g
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user