From ceb7f5b6785488d0673fdeeb932220e9aeae1f97 Mon Sep 17 00:00:00 2001 From: edmccard Date: Sun, 8 Apr 2012 21:06:38 -0400 Subject: [PATCH] Decouple test sstup code from test running code --- test/base.d | 771 ++++++++++++++++++++++++++++++++++++++++++- test/test_bus.d | 690 +------------------------------------- test/test_cpu_all.sh | 4 + test/test_decimal.d | 49 +-- test/test_func.d | 90 +---- test/testopts | 2 +- 6 files changed, 804 insertions(+), 802 deletions(-) create mode 100755 test/test_cpu_all.sh diff --git a/test/base.d b/test/base.d index 321eca4..aa4f153 100644 --- a/test/base.d +++ b/test/base.d @@ -1,7 +1,8 @@ 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; @@ -2562,3 +2563,771 @@ if (isCpu!T) } 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); +} diff --git a/test/test_bus.d b/test/test_bus.d index 9a1008b..909acb9 100644 --- a/test/test_bus.d +++ b/test/test_bus.d @@ -1,696 +1,10 @@ module test.test_bus; -import std.algorithm, std.array, std.conv, std.exception, std.stdio, - std.string; +import test.base, test.cpu; -import test.base, test.cpu, test.opcodes; - -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 +void main() { auto report = report_timing_debug(); diff --git a/test/test_cpu_all.sh b/test/test_cpu_all.sh new file mode 100755 index 0000000..b3a11df --- /dev/null +++ b/test/test_cpu_all.sh @@ -0,0 +1,4 @@ +rdmd @testopts test_func.d +rdmd @testopts test_bus.d +rdmd @testopts test_decimal.d + diff --git a/test/test_decimal.d b/test/test_decimal.d index 9c3bc8e..731c187 100644 --- a/test/test_decimal.d +++ b/test/test_decimal.d @@ -226,30 +226,6 @@ if (isCpu!T) } -unittest -{ - writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.no)"); - testDecimalMode!(CPU!("6502", false, false))(); - writeln("Testing decimal mode, CMOS(Strict.no, Cumulative.no)"); - testDecimalMode!(CPU!("65C02", false, false))(); - - writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.yes)"); - testDecimalMode!(CPU!("6502", false, true))(); - 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))(); -} - - version(Benchmark) { import std.datetime, std.stdio; @@ -266,3 +242,28 @@ version(Benchmark) writeln(milliExpected / r[0].to!("msecs", int)); } } +else +{ + void main() + { + writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.no)"); + testDecimalMode!(CPU!("6502", false, false))(); + writeln("Testing decimal mode, CMOS(Strict.no, Cumulative.no)"); + testDecimalMode!(CPU!("65C02", false, false))(); + + writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.yes)"); + testDecimalMode!(CPU!("6502", false, true))(); + 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))(); + } +} diff --git a/test/test_func.d b/test/test_func.d index 1743c7a..8bb8fb6 100644 --- a/test/test_func.d +++ b/test/test_func.d @@ -1,96 +1,10 @@ module test.test_func; -import std.string; - -import test.base, test.cpu, test.opcodes; +import test.base, test.cpu; -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); -} - - -unittest +void main() { auto report = report_debug(); diff --git a/test/testopts b/test/testopts index eded843..58f90ff 100644 --- a/test/testopts +++ b/test/testopts @@ -1,2 +1,2 @@ --unittest -I.. -I../src -g +-I.. -I../src -g