From f79021fc14778cf0d032304c2416fede54d8fefc Mon Sep 17 00:00:00 2001 From: edmccard Date: Mon, 2 Apr 2012 20:58:51 -0400 Subject: [PATCH] Test refactoring; complete timing and nearly complete functional --- test/base.d | 2927 ++++++++++++++++++++++++++++++++++++---------- test/cpu.d | 243 +++- test/opcodes.d | 6 +- test/test_bus.d | 132 ++- test/test_func.d | 104 ++ 5 files changed, 2713 insertions(+), 699 deletions(-) create mode 100644 test/test_func.d diff --git a/test/base.d b/test/base.d index f49aa58..384fd0f 100644 --- a/test/base.d +++ b/test/base.d @@ -10,14 +10,17 @@ import test.cpu, test.opcodes; * Emulates zero page, stack, and 3 additional pages of "main memory" * starting at a user-defined address. Accesses outside the defined * address space raise an exception. + * + * The contents are initialized to 0xFF. Individual locations can be + * read and written using array index syntax. */ struct TestMemory { private: - ubyte[0x200] data1; - ubyte[0x300] data2; - immutable ushort data2_base; - immutable size_t data2_max; + ubyte[0x200] data1 = 0xFF; + ubyte[0x300] data2 = 0xFF; + ushort data2_base; + size_t data2_max; public: /* @@ -30,7 +33,7 @@ public: * block with a base address greater than 0x01FF (there must be at * least one such block). */ - this(const Block[] blocks ...) + this(const(Block[]) blocks ...) { foreach (block; blocks) { @@ -61,7 +64,7 @@ public: enforce(data2_base, "Missing memory > 0x0200"); } - ubyte read(ushort addr) + ubyte read(ushort addr) const { if (addr < 0x0200) return data1[addr]; @@ -81,708 +84,2306 @@ public: throw new TestException(format("write %0.4x", addr)); } - ubyte opIndex(size_t i1) + ubyte opIndex(size_t i1) const { auto addr = cast(ushort)i1; enforce(addr < 0x0200 || (addr >= data2_base && addr < data2_max), "Read out of bounds"); return read(addr); } + + ubyte opIndexAssign(ubyte val, size_t i1) + { + auto addr = cast(ushort)i1; + enforce(addr < 0x0200 || (addr >= data2_base && addr < data2_max), + "Write out of bounds"); + write(addr, val); + return val; + } } -/* - * A block of memory with a given base address. - */ + +// A block of memory with a given base address. struct Block { - ushort base; - ubyte[] data; + const ushort base; + const(ubyte[]) data; string toString() const { - return format("Block(%0.4X, %s)", base, formatMemory()); - } - - string formatMemory(int max = 3) const - { - if (max > data.length) max = data.length; - auto hexbytes = map!(`format("%0.2X", a)`)(data[0..max]); - auto ret = join(array(hexbytes), " "); - if (data.length > max) - ret ~= format(" (%d more bytes)", data.length - max); - return "[" ~ ret ~ "]"; + return format("Block(%0.4X, [%s])", base, formatMemory(data)); } } -template addrsetup_t(T) +/* + * Formats data as a string of 2-digit hex bytes, separated by spaces. + * + * If data is longer than max, the string will end with an indication + * of the number of extra bytes. + */ +string formatMemory(const(ubyte[]) data, int max = 3) { - alias Block[] delegate(T, out ushort, out string) addrsetup_t; -} - -template datasetup_t(T) -{ - alias Block[] delegate(T, ushort, out string) datasetup_t; + if (max > data.length) max = data.length; + auto hexbytes = map!(`format("%0.2X", a)`)(data[0..max]); + auto ret = join(array(hexbytes), " "); + if (data.length > max) + ret ~= format(" (%d more bytes)", data.length - max); + return ret; } -auto setup_address_none(T)(ubyte opcode) -if (isCpu!T) +struct OpInfo { - return cast(addrsetup_t!T[])[]; + ushort addr; + ubyte data; + int len; + bool write; } -auto setup_address_imm(T)(ubyte opcode) -if (isCpu!T) -{ - assert(IMM_OPS!T.canFind(opcode)); +alias void delegate(ubyte, CpuInfo, Block[], OpInfo, string, TestSetup*) + testsetup; - auto setup(T cpu, out ushort addr, out string name) +class TestSetup +{ + testsetup setup; + TestSetup next; + + auto static opCall(testsetup d) { - name = "imm"; - addr = 0x1001; - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode])]; + auto obj = new TestSetup(); + obj.setup = d; + return obj; } - return [&setup]; + void run(ubyte opcode, CpuInfo cpu = CpuInfo(), Block[] data = [], + OpInfo info = OpInfo(), string msg = "") + { + setup(opcode, cpu, data, info, msg, &next); + } } -auto setup_address_zpg(T)(ubyte opcode) -if (isCpu!T) +TestSetup connect(TestSetup first, TestSetup[] rest...) { - assert(ZPG_OPS!T.canFind(opcode)); - - auto setup(T cpu, out ushort addr, out string name) + if (!(rest.empty)) { - name = "zpg"; - addr = 0x0070; - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0x70])]; + auto x = first; + while (x.next !is null) x = x.next; + x.next = connect(rest[0], rest[1..$]); } - - return [&setup]; + return first; } -auto setup_address_zpxy(T)(ubyte opcode) -if (isCpu!T) +template testCallNext() { - bool useX = ZPX_OPS!T.canFind(opcode); - assert(useX || ZPY_OPS!T.canFind(opcode)); - - auto setup(T cpu, out ushort addr, out string name, ubyte idx, - ubyte opcode) + void callNext(string newMsg = "", Block[] newData = []) { - name = (useX ? "zpx" : "zpy"); - addr = pageWrapAdd(0x0070, idx); - if (useX) - { - setX(cpu, idx); setY(cpu, 0x10); - } - else - { - setY(cpu, idx); setX(cpu, 0x10); - } - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0x70])]; + if (*next !is null) + next.run(opcode, cpu, data ~ newData, info, msg ~ newMsg); } - - auto setup_nowrap(T cpu, out ushort addr, out string name) - { - auto ret = setup(cpu, addr, name, 0x20, opcode); - name ~= " no-wrap"; - return ret; - } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - auto ret = setup(cpu, addr, name, 0xA0, opcode); - name ~= " wrap"; - return ret; - } - - return [&setup_nowrap, &setup_wrap]; } -auto setup_address_abs(T)(ubyte opcode) -if (isCpu!T) + +// Does nothing. +auto setup_none() { - assert(ABS_OPS!T.canFind(opcode)); - - auto setup(T cpu, out ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "abs"; - addr = 0x10C5; - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0xC5, 0x10])]; + if (*next !is null) next.run(opcode, cpu, data, info, msg); } - - return [&setup]; + return TestSetup(&setup); } -auto setup_address_zpi(T)(ubyte opcode) -if (isCpu!T && isCMOS!T) + +// Aborts a test. +auto setup_op_abort() { - assert(ZPI_OPS!T.canFind(opcode)); - - auto setup_nowrap(T cpu, out ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "zpi no-wrap"; - addr = 0x10C5; - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0x70]), - Block(0x0070, [0xC5, 0x10])]; } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - name = "zpi wrap"; - addr = 0x10C5; - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0xFF]), - Block(0x00FF, [0xC5]), - Block(0x0000, [0x10])]; - } - - return [&setup_nowrap, &setup_wrap]; + return TestSetup(&setup); } -auto setup_address_izx(T)(ubyte opcode) -if (isCpu!T) + +// Prints the current message. +auto setup_debug() { - assert(IZX_OPS!T.canFind(opcode)); - - auto setup_nowrap(T cpu, out ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "izx no-wrap"; - addr = 0x10C5; - setPC(cpu, 0x1000); - setX(cpu, 0x20); - return [Block(0x1000, [opcode, 0x70]), - Block(0x0090, [0xC5, 0x10])]; + import std.stdio; + writeln(format("%s\n data %s\n addr %0.4X op %0.2X %s", + msg, data, info.addr, opcode, cpu)); + if (*next !is null) next.run(opcode, cpu, data, info, msg); } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - name = "izx wrap"; - addr = 0x10C5; - setPC(cpu, 0x1000); - setX(cpu, 0x90); - return [Block(0x1000, [opcode, 0x70]), - Block(0x00FF, [0xC5]), - Block(0x0000, [0x10])]; - } - - return [&setup_nowrap, &setup_wrap]; + return TestSetup(&setup); } -auto setup_address_izy(T)(ubyte opcode) -if (isCpu!T) + +// Splits with the given flag set, then cleared. +auto setup_flag(Flag f) { - assert(IZY_OPS!T.canFind(opcode)); - - auto setup_nowrap(T cpu, out ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "izy no-wrap"; - addr = pageCrossAdd(0x10C5, 0x20); - setY(cpu, 0x20); - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0x70]), - Block(0x0070, [0xC5, 0x10])]; - } + mixin testCallNext; + clearFlag(cpu, f); + callNext(format("%s0 ", flagToString(f))); - auto setup_wrap(T cpu, out ushort addr, out string name) - { - name = "izy wrap"; - addr = pageCrossAdd(0x10C5, 0x20); - setY(cpu, 0x20); - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0xFF]), - Block(0x00FF, [0xC5]), - Block(0x0000, [0x10])]; + setFlag(cpu, f); + callNext(format("%s1 ", flagToString(f))); } - - auto setup_px(T cpu, out ushort addr, out string name) - { - name = "izy px"; - addr = pageCrossAdd(0x10C5, 0x50); - setY(cpu, 0x50); - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0x70]), - Block(0x070, [0xC5, 0x10])]; - } - - return [&setup_nowrap, &setup_wrap, &setup_px]; + return TestSetup(&setup); } -auto setup_address_abxy(T)(ubyte opcode) -if (isCpu!T) + +// Splits with all flags set, then all flags cleared. +auto setup_mask_flags() { - bool useX = ABX_OPS!T.canFind(opcode); - assert(useX || ABY_OPS!T.canFind(opcode)); - - auto setup(T cpu, out ushort addr, out string name, ubyte idx, - ubyte opcode) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = (useX ? "abx" : "aby"); - addr = pageCrossAdd(0x10C5, idx); - if (useX) - { - setX(cpu, idx); setY(cpu, 0x10); - } - else - { - setY(cpu, idx); setX(cpu, 0x10); - } - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode, 0xC5, 0x10])]; + mixin testCallNext; + clearFlag(cpu, Flag.C, Flag.Z, Flag.I, Flag.D, Flag.V, Flag.N); + callNext("F0 "); + setFlag(cpu, Flag.C, Flag.Z, Flag.I, Flag.D, Flag.V, Flag.N); + callNext("F1 "); } - - auto setup_no_px(T cpu, out ushort addr, out string name) - { - auto ret = setup(cpu, addr, name, 0x20, opcode); - name ~= " no-px"; - return ret; - } - - auto setup_px(T cpu, out ushort addr, out string name) - { - auto ret = setup(cpu, addr, name, 0x50, opcode); - name ~= " px"; - return ret; - } - - return [&setup_no_px, &setup_px]; + return TestSetup(&setup); } -auto setup_address_reg(T)(ubyte opcode) -if (isCpu!T) + +/* + * Used after setup_mask_flags, guarantees coverage of all + * combinations of N and Z. + */ +auto setup_nz() { - static if (isNMOS!T) - assert(REG_OPS!T.canFind(opcode)); - else - assert(REG_OPS!T.canFind(opcode) || NOP1_OPS!T.canFind(opcode)); - - auto setup(T cpu, out ushort addr, out string name) - { - name = "register"; - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode])]; - } - - return [&setup]; + return setup_flag(Flag.N); } -auto setup_address_push(T)(ubyte opcode) -if (isCpu!T) + +/* + * Can be used after setup_mask_flags to cover all combinations of + * flags. + */ +auto setup_nvdzc() { - assert(PUSH_OPS!T.canFind(opcode)); + return connect(setup_flag(Flag.N), setup_flag(Flag.Z), + setup_flag(Flag.V), setup_flag(Flag.D), + setup_flag(Flag.C)); +} - auto setup_nowrap(T cpu, out ushort addr, out string name) + +// Splits with info.data 0x00, 0x40, and 0x80. +auto setup_data() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "non-wrapping"; - setSP(cpu, 0xFE); - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode])]; + mixin testCallNext; + info.data = 0x00; + callNext("zero "); + + info.data = 0x40; + callNext("positive "); + + info.data = 0x80; + callNext("negative "); } + return connect(TestSetup(&setup)); +} - auto setup_wrap(T cpu, out ushort addr, out string name) + +/* + * For register transfer opcodes. + * + * Splits on 0x00, 0x40, and 0x80 in the source register. + */ +auto setup_op_transfer(Reg source, Reg dest) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "wrapping"; + mixin testCallNext; + ubyte other = (info.data > 0x00 && info.data < 0x80) ? 0x00 : 0x01; + setX(cpu, other); setY(cpu, other); setA(cpu, other); + setReg(cpu, source, info.data); + callNext("xfer "); + } + return connect(setup_nz(), setup_data(), TestSetup(&setup)); +} + + +// For TXS and TSX. +auto setup_op_stack_xfer() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; setSP(cpu, 0x00); - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode])]; + setX(cpu, 0xFF); + callNext("TXS "); } - - return [&setup_nowrap, &setup_wrap]; + return connect(setup_nz(), TestSetup(&setup)); } -auto setup_address_pull(T)(ubyte opcode) -if (isCpu!T) + +/* + * For implied address mode. + * + * Sets PC to 0x1000 and puts a (1-byte) opcode in memory. + */ +auto setup_addr_implied() { - assert(PULL_OPS!T.canFind(opcode)); - - auto setup_nowrap(T cpu, out ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = "non-wrapping"; - setSP(cpu, 0x01); + mixin testCallNext; setPC(cpu, 0x1000); - return [Block(0x1000, [opcode])]; + info.len = 1; + callNext("", [Block(0x1000, [opcode])]); } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - name = "wrapping"; - setSP(cpu, 0xFF); - setPC(cpu, 0x1000); - return [Block(0x1000, [opcode])]; - } - - return [&setup_nowrap, &setup_wrap]; + return TestSetup(&setup); } -auto setup_address_branch(T)(ubyte opcode) -if (isCpu!T) +/* + * For immediate address mode. + * + * Sets PC to 0x1000 and info.addr to 0x1001. + */ +auto setup_addr_imm() { - assert(BRANCH_OPS!T.canFind(opcode)); - - int count; - static string[5] names = - ["no-branch", "forward", "forward-px", "backward", "backward-px"]; - static ubyte[5] values = [0x10, 0x10, 0x7F, 0xFE, 0xF5]; - - auto setup(T cpu, out ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = names[count]; - if (name == "no-branch") - { - if (isNMOS!T || opcode != 0x80) - expectNoBranch(cpu, opcode); - } - else - { - expectBranch(cpu, opcode); - } - if (name == "forward-px") - { - setPC(cpu, 0x1081); - return [Block(getPC(cpu), [opcode, values[count++]])]; - } - else + mixin testCallNext; + setPC(cpu, 0x1000); + info.len = 2; + info.addr = 0x1001; + callNext("imm ", [Block(0x1000, [opcode])]); + } + return TestSetup(&setup); +} + + +/* + * For branch opcodes. + */ +auto setup_addr_branch(bool cmos) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + if (!cmos || opcode != 0x80) { + expectNoBranch(cpu, opcode); setPC(cpu, 0x1000); - return [Block(getPC(cpu), [opcode, values[count++]])]; + info.addr = 0x1002; + callNext("no-branch ", [Block(0x1000, [opcode, 0x10])]); } - } - return [&setup, &setup, &setup, &setup, &setup]; + expectBranch(cpu, opcode); + + setPC(cpu, 0x1000); + info.addr = 0x1012; + callNext("branch-fwd ", [Block(0x1000, [opcode, 0x10])]); + + setPC(cpu, 0x1081); + info.addr = 0x1102; + callNext("branch-fwd-px ", [Block(0x1081, [opcode, 0x7F])]); + + setPC(cpu, 0x1000); + info.addr = 0x1000; + callNext("branch-bkwd ", [Block(0x1000, [opcode, 0xFE])]); + + setPC(cpu, 0x1100); + info.addr = 0x10F7; + callNext("branch-bkwd-px ", [Block(0x1000, []), + Block(0x1100, [opcode, 0xF5])]); + } + return TestSetup(&setup); } -auto setup_address_op_BRK(T)(ubyte opcode) -if (isCpu!T) -{ - assert(opcode == 0x00); - auto setup(T cpu, out ushort addr) +/* + * For zeropage adddress mode. + * + * Sets PC to 0x1000 and puts [opcode, 0x70] in memory. + */ +auto setup_addr_zpg() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - addr = 0xFE55; + mixin testCallNext; + setPC(cpu, 0x1000); + info.len = 2; + info.addr = 0x0070; + callNext("zpg ", [Block(0x1000, [opcode, 0x70])]); + } + return TestSetup(&setup); +} + + +/* + * For absolute address mode. + * + * Sets PC to 0x1000 and puts [opcode, 0xC5, 0x10] in memory. + */ +auto setup_addr_abs() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setPC(cpu, 0x1000); + info.len = 3; + info.addr = 0x10C5; + callNext("abs ", [Block(0x1000, [opcode, 0xC5, 0x10])]); + } + return TestSetup(&setup); +} + + +/* + * For zeropage,x/y address modes. + * + * Sets PC to 0x1000 and puts [opcode, 0x70] in memory. + */ +auto setup_addr_zpxy(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + string name = (reg == Reg.X ? "zpx " : "zpy "); + info.len = 2; + setPC(cpu, 0x1000); + + setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10); + setReg(cpu, reg, 0x00); + info.addr = pageWrapAdd(0x0070, 0x00); + callNext(name ~ "no-idx ", [Block(0x1000, [opcode, 0x70])]); + + setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10); + setReg(cpu, reg, 0x20); + info.addr = pageWrapAdd(0x0070, 0x20); + callNext(name ~ "no-wrap ", [Block(0x1000, [opcode, 0x70])]); + + setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10); + setReg(cpu, reg, 0xA0); + info.addr = pageWrapAdd(0x0070, 0xA0); + callNext(name ~ "wrap ", [Block(0x1000, [opcode, 0x70])]); + } + return TestSetup(&setup); +} + + +// For absolute,x/y address modes. +auto setup_addr_abxy(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + string name = (reg == Reg.X ? "abx " : "aby "); + info.len = 3; + setPC(cpu, 0x1000); + + setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10); + setReg(cpu, reg, 0x00); + info.addr = pageCrossAdd(0x10C5, 0x00); + callNext(name ~ "no-idx ", [Block(0x1000, [opcode, 0xC5, 0x10])]); + + setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10); + setReg(cpu, reg, 0x20); + info.addr = pageCrossAdd(0x10C5, 0x20); + callNext(name ~ "no-px ", [Block(0x1000, [opcode, 0xC5, 0x10])]); + + setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10); + setReg(cpu, reg, 0x50); + info.addr = pageCrossAdd(0x10C5, 0x50); + callNext(name ~ "px ", [Block(0x1000, [opcode, 0xC5, 0x10])]); + } + return TestSetup(&setup); +} + + +// For zeropage indirect,x address mode. +auto setup_addr_izx() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.addr = 0x10C5; + info.len = 2; + setPC(cpu, 0x1000); + setA(cpu, 0x01); setY(cpu, 0x01); + + setX(cpu, 0x20); + callNext("izx no-wrap ", [Block(0x1000, [opcode, 0x70]), + Block(0x0090, [0xC5, 0x10])]); + + setX(cpu, 0xA0); + callNext("izx wrap ", [Block(0x1000, [opcode, 0x70]), + Block(0x0010, [0xC5, 0x10])]); + + setX(cpu, 0x8F); + callNext("izx px ", [Block(0x1000, [opcode, 0x70]), + Block(0x00FF, [0xC5]), + Block(0x0000, [0x10])]); + } + return TestSetup(&setup); +} + + +// For zeropage indirect,y address mode. +auto setup_addr_izy() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.len = 2; + setPC(cpu, 0x1000); + setA(cpu, 0x01); setX(cpu, 0x01); + + info.addr = pageCrossAdd(0x10C5, 0x20); + setY(cpu, 0x20); + + callNext("izy no-wrap ", [Block(0x1000, [opcode, 0x70]), + Block(0x0070, [0xC5, 0x10])]); + + callNext("izy wrap ", [Block(0x1000, [opcode, 0xFF]), + Block(0x00FF, [0xC5]), + Block(0x0000, [0x10])]); + + info.addr = pageCrossAdd(0x10C5, 0x50); + setY(cpu, 0x50); + callNext("izy px ", [Block(0x1000, [opcode, 0x70]), + Block(0x0070, [0xC5, 0x10])]); + } + return TestSetup(&setup); +} + + +// For zeropage indirect address mode. +auto setup_addr_zpi() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.len = 2; + info.addr = 0x10C5; + setPC(cpu, 0x1000); + + callNext("zpi no-wrap ", [Block(0x1000, [opcode, 0x70]), + Block(0x0070, [0xC5, 0x10])]); + + callNext("zpi wrap ", [Block(0x1000, [opcode, 0xFF]), + Block(0x00FF, [0xC5]), + Block(0x0000, [0x10])]); + } + return TestSetup(&setup); +} + +/* + * Splits with the SP at 0xFF, and then at 0x00 (to cover the case of + * pull operations when the stack is "empty"). + */ +auto setup_pull_wrap() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setSP(cpu, 0xFF); + callNext("wrap "); + + setSP(cpu, 0x00); + callNext("no-wrap "); + } + return TestSetup(&setup); +} + + +/* + * For pull opcodes. + * + * Splits with 0x00, 0x40, and 0x80 at the top of the stack (to + * exercise different potential combinations of N and Z). + */ +auto setup_op_pull(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + auto sp = pageWrapAdd(getSP(cpu), 1); + ubyte other = (info.data > 0x00 && info.data < 0x80) ? 0x00 : 0x01; + setX(cpu, other); setY(cpu, other); setA(cpu, other); + callNext("pull ", [Block(sp, [info.data])]); + } + return connect(setup_pull_wrap(), setup_nz(), setup_data(), + TestSetup(&setup)); +} + + +/* + * Splits with each possible meaningful status byte value on the + * stack, and with values with break/reserved bits set. + */ +auto setup_status() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 16; j++) + { + info.data = cast(ubyte)(i * 0x40 + j); + callNext(format("S %0.2X ", info.data | 0x30)); + } + } + + string[] msgs = ["B ", "R ", "BR "]; + foreach (val; [0x10, 0x20, 0x30]) + { + info.data = cast(ubyte)val; + callNext("S " ~ msgs[(info.data >> 4) - 1]); + } + } + return TestSetup(&setup); +} + + +// For PLP. +auto setup_op_PLP() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + auto sp = pageWrapAdd(getSP(cpu), 1); + setStatus(cpu, ~info.data); + callNext("PLP ", [Block(sp, [info.data])]); + } + return connect(setup_pull_wrap(), setup_status(), TestSetup(&setup)); +} + + +/* + * Splits with the SP at 0x00, and then at 0xFF (to cover the case of + * push operations when the stack is "full"). + */ +auto setup_push_wrap() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setSP(cpu, 0x00); + callNext("wrap "); + + setSP(cpu, 0xFF); + callNext("no-wrap "); + } + return TestSetup(&setup); +} + + +/* + * For push opcodes. + * + * Splits with 0x00, 0x40, and 0x80 in the appropriate register. + */ +auto setup_op_push(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setX(cpu, 0x01); setY(cpu, 0x01); setA(cpu, 0x01); + setReg(cpu, reg, info.data); + callNext("push ", [Block(getSP(cpu), [~info.data])]); + } + return connect(setup_push_wrap(), setup_data(), TestSetup(&setup)); +} + + +/* + * For PHP. + * + * Puts a garbage value (that does not correspond to the current + * status) just below the top of the stack. + */ +auto setup_op_PHP() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.data = getStatus(cpu); + callNext("PHP ", [Block(getSP(cpu), [~info.data])]); + } + return connect(setup_nvdzc(), setup_push_wrap(), TestSetup(&setup)); +} + + +/* + * For load opcodes. + * + * Splits with 0x00, 0x40, and 0x80 in the source memory location. + */ +auto setup_op_load(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + callNext("load ", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(), setup_data(), TestSetup(&setup)); +} + + +// Splits with 0x00, 0x40, 0x80 in the source register. +auto setup_op_store(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setReg(cpu, reg, info.data); + callNext("store ", [Block(info.addr, [~info.data])]); + } + return connect(setup_data(), TestSetup(&setup)); +} + + +// For SAX. +auto setup_op_SAX() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + if (opcode == 0x83) + { + setA(cpu, 0x00); + callNext("zero SAX "); + + setA(cpu, 0x2B); + callNext("positive SAX "); + + setA(cpu, 0x8B); + callNext("negative SAX "); + } + else + { + setA(cpu, 0x00); setX(cpu, 0x00); + callNext("zero SAX "); + + setA(cpu, 0x10); setX(cpu, 0x01); + callNext("zero SAX "); + + setA(cpu, 0x0B); setX(cpu, 0x0D); + callNext("positive SAX "); + + setA(cpu, 0x8B); setX(cpu, 0x8D); + callNext("negative SAX "); + } + } + return TestSetup(&setup); +} + + +// For STZ. +auto setup_op_STZ() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.data = 0; + callNext("STZ ", [Block(info.addr, [~info.data])]); + } + return TestSetup(&setup); +} + + +// setup_bit_val, puts in memory; +auto setup_bit_data() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.data = 0x80; + callNext("N1 ", [Block(info.addr, [info.data])]); + + info.data = 0x40; + callNext("V1 ", [Block(info.addr, [info.data])]); + + info.data = 0xC0; + callNext("N1V1 ", [Block(info.addr, [info.data])]); + + info.data = 0x20; + callNext("N0V0 ", [Block(info.addr, [info.data])]); + } + return TestSetup(&setup); +} + + +// For BIT. +auto setup_op_BIT() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, info.data); + callNext("BIT non-zero "); + + setA(cpu, ~info.data); + callNext("BIT zero"); + } + return connect(setup_nz(), setup_flag(Flag.V), setup_bit_data(), + TestSetup(&setup)); +} + + +// For TRB. +auto setup_op_TRB() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0); setX(cpu, 0xFF); setY(cpu, 0xFF); + info.data = 0x01; + callNext("TRB Z1 1 ", [Block(info.addr, [info.data])]); + + setA(cpu, 0x40); setX(cpu, 0x00); setY(cpu, 0x00); + info.data = 0x40; + callNext("TRB Z0 0 ", [Block(info.addr, [info.data])]); + + setA(cpu, 0x40); setX(cpu, 0x00); setY(cpu, 0x00); + info.data = 0x41; + callNext("TRB Z0 1 ", [Block(info.addr, [info.data])]); + } + return TestSetup(&setup); +} + + +// For TSB. +auto setup_op_TSB() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x01); setX(cpu, 0x00); setY(cpu, 0x00); + info.data = 0x00; + callNext("TSB Z1 1 ", [Block(info.addr, [info.data])]); + + setA(cpu, 0x02); setX(cpu, 0x00); setY(cpu, 0x00); + info.data = 0x02; + callNext("TSB Z0 2 ", [Block(info.addr, [info.data])]); + + setA(cpu, 0x30); setX(cpu, 0x00); setY(cpu, 0x00); + info.data = 0x80; + callNext("TSB Z1 B0 ", [Block(info.addr, [info.data])]); + + setA(cpu, 0x00); setX(cpu, 0xFF); setY(cpu, 0xFF); + info.data = 0x02; + callNext("TSB Z1 2 ", [Block(info.addr, [info.data])]); + } + return TestSetup(&setup); +} + + +// For BRK. +auto setup_op_BRK() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.addr = 0xFE55; setPC(cpu, 0xFD00); + ushort ret = 0xFD02; auto sp = getSP(cpu); auto sp1 = pageWrapAdd(sp, -1); - return [Block(0xFD00, [0x00]), - Block(sp, [notXX(0xFD)]), - Block(sp1, [notXX(0x00)]), - // sp2 set by setup_data_op_BRK - Block(0xFFFE, [0x55, 0xFE])]; - } + auto sp2 = pageWrapAdd(sp, -2); + info.data = getStatus(cpu); + callNext("BRK ", [Block(0xFD00, [opcode]), + Block(sp, [~addrHi(ret)]), + Block(sp1, [~addrLo(ret)]), + Block(sp2, [~info.data]), + Block(0xFFFE, [0x55, 0xFE])]); - auto setup_nowrap(T cpu, out ushort addr, out string name) - { - name = "BRK no-wrap"; - setSP(cpu, 0xFF); - return setup(cpu, addr); } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - name = "BRK wrap"; - setSP(cpu, 0x01); - return setup(cpu, addr); - } - - return [&setup_nowrap, &setup_wrap]; + return connect(setup_nvdzc(), setup_push_wrap(), TestSetup(&setup)); } -auto setup_address_op_JSR(T)(ubyte opcode) -if (isCpu!T) -{ - assert(opcode == 0x20); - auto setup(T cpu, out ushort addr) +auto setup_RTx(bool isRTI) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - addr = 0x10C5; + mixin testCallNext; + string name = (isRTI ? "RTI " : "RTS "); + info.addr = 0x1211; setPC(cpu, 0x1000); auto sp = getSP(cpu); + auto sp1 = pageWrapAdd(sp, (isRTI ? 1 : 0)); + auto sp2 = pageWrapAdd(sp, (isRTI ? 2 : 1)); + auto sp3 = pageWrapAdd(sp, (isRTI ? 3 : 2)); + callNext(name, [Block(0x1000, [opcode])] ~ + (isRTI ? [Block(sp1, [info.data])] : []) ~ + [Block(sp2, [addrLo(info.addr)]), + Block(sp3, [addrHi(info.addr)])]); + } + return connect(setup_pull_wrap(), TestSetup(&setup)); +} + + +// For RTI. +auto setup_op_RTI() +{ + return connect(setup_status(), setup_RTx(true)); +} + + +// For RTS. +auto setup_op_RTS() +{ + return setup_RTx(false); +} + + +// For JMP inx. +auto setup_op_JMP_inx() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.addr = 0x1234; + info.len = 3; + setPC(cpu, 0x1000); + + setX(cpu, 0x20); + callNext("JMP inx A ", [Block(0x1000, [opcode, 0xC5, 0x10]), + Block(0x10E5, [0x34, 0x12])]); + + setX(cpu, 0x40); + callNext("JMP inx B ", [Block(0x1000, [opcode, 0xC5, 0x10]), + Block(0x1105, [0x34, 0x12])]); + + setX(cpu, 0x30); + callNext("JMP inx C ", [Block(0x1000, [opcode, 0xCF, 0x10]), + Block(0x10FF, [0x34, 0x12])]); + } + return TestSetup(&setup); +} + + +// For JMP ind. +auto setup_op_JMP_ind(bool cmos) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.addr = 0x1234; + info.len = 3; + setPC(cpu, 0x1000); + setX(cpu, 0x40); setY(cpu, 0x40); setA(cpu, 0x40); + + callNext("JMP ind px ", [Block(0x1000, [opcode, 0xFF, 0x11])] ~ + (cmos ? [Block(0x11FF, [0x34, 0x12])] + : [Block(0x11FF, [0x34]), + Block(0x1100, [0x12])])); + + callNext("JMP ind no px ", [Block(0x1000, [opcode, 0xC5, 0x10]), + Block(0x10C5, [0x34, 0x12])]); + } + return TestSetup(&setup); +} + + +// For JSR. +auto setup_op_JSR() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.addr = 0x10C5; + setPC(cpu, 0x1000); + ushort ret = 0x1002; + auto sp = getSP(cpu); auto sp1 = pageWrapAdd(sp, 1); - return [Block(0x1000, [0x20, 0xC5, 0x10]), - Block(sp, [notXX(0x10)]), - Block(sp1, [notXX(0x02)])]; - + callNext("JSR ", [Block(0x1000, [opcode, 0xC5, 0x10]), + Block(sp, [~addrHi(ret)]), + Block(sp, [~addrLo(ret)])]); } - - auto setup_nowrap(T cpu, out ushort addr, out string name) - { - name = "JSR no-wrap"; - setSP(cpu, 0xFF); - return setup(cpu, addr); - } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - name = "JSR wrap"; - setSP(cpu, 0x00); - return setup(cpu, addr); - } - - return [&setup_nowrap, &setup_wrap]; + return connect(setup_push_wrap(), TestSetup(&setup)); } -auto setup_address_op_RTx(T)(ubyte opcode) -if (isCpu!T) +// For CMOS opcode 5C. +auto setup_op_5C() { - assert(opcode == 0x40 || opcode == 0x60); - - auto setup(T cpu, out ushort addr, out string name, ubyte opcode) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = (opcode == 0x40 ? "RTI" : "RTS"); - addr = 0x1211; - setPC(cpu, 0x1000); - auto sp = getSP(cpu); - auto sp2 = pageWrapAdd(sp, (opcode == 0x40 ? 2 : 1)); - auto sp3 = pageWrapAdd(sp, (opcode == 0x40 ? 3 : 2)); - return [Block(0x1000, [opcode]), - // sp1 set by setup_data_op_RTI for opcode 0x40 - Block(sp2, [0x11]), - Block(sp3, [0x12])]; - } - - auto setup_nowrap(T cpu, out ushort addr, out string name) - { - setSP(cpu, 0xF0); - auto ret = setup(cpu, addr, name, opcode); - name ~= " no-wrap"; - return ret; - } - - auto setup_wrap(T cpu, out ushort addr, out string name) - { - setSP(cpu, 0xFE); - auto ret = setup(cpu, addr, name, opcode); - name ~= " wrap"; - return ret; - } - - return [&setup_nowrap, &setup_wrap]; -} - -auto setup_address_op_5C(T)(ubyte opcode) -if (isCpu!T && isCMOS!T) -{ - assert(opcode == 0x5C); - - auto setup(T cpu, out ushort addr, out string name) - { - name = "NOP8"; + mixin testCallNext; + info.len = 3; setPC(cpu, 0xFD00); - return [Block(0xFD00, [0x5C, 0x72])]; + callNext("NOP8 ", [Block(0xFD00, [0x5C, 0x72])]); } - - return [&setup]; -} - -auto setup_address_op_JMP_abs(T)(ubyte opcode) -if (isCpu!T) -{ - assert(opcode == 0x4C); - - auto setup(T cpu, out ushort addr, out string name) - { - name = "JMP abs"; - addr = 0x10C5; - setPC(cpu, 0x1000); - return [Block(0x1000, [0x4C, 0xC5, 0x10])]; - } - - return [&setup]; -} - -auto setup_address_op_JMP_ind(T)(ubyte opcode) -if (isCpu!T) -{ - assert(opcode == 0x6C); - - auto setup_nopx(T cpu, out ushort addr, out string name) - { - name = "JMP ind no-px"; - addr = 0x1234; - setPC(cpu, 0x1000); - return [Block(0x1000, [0x6C, 0xC5, 0x10]), - Block(0x10C5, [0x34, 0x12])]; - } - - auto setup_px(T cpu, out ushort addr, out string name) - { - name = "JMP ind px"; - addr = 0x1234; - setPC(cpu, 0x1000); - ushort ial = 0x11FF; - ushort iah = (isNMOS!T ? 0x1100 : 0x1200); - return [Block(0x1000, [0x6C, 0xFF, 0x11]), - Block(ial, [0x34]), - Block(iah, [0x12])]; - } - - return [&setup_nopx, &setup_px]; -} - -auto setup_address_op_JMP_inx(T)(ubyte opcode) -if (isCpu!T && isCMOS!T) -{ - assert(opcode == 0x7C); - - auto setup(T cpu, out ushort addr, out string name) - { - name = "JMP inx"; - addr = 0x1234; - setPC(cpu, 0x1000); - setX(cpu, 0x20); - return [Block(0x1000, [0x7C, 0xC5, 0x10]), - Block(0x10C5, [0x14, 0x12])]; - } - - return [&setup]; + return TestSetup(&setup); } - -// XXX data not anything put in memory by setup_address_* ? - -auto setup_data_none(T)(ubyte opcode) -if (isCpu!T) +auto setup_dec_data() { - auto setup(T cpu, ushort addr, out string name) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - name = ""; - return cast(Block[])[]; - } + mixin testCallNext; + info.data = 0x01; + callNext("zero "); - return [&setup]; + info.data = 0x00; + callNext("negative "); + + info.data = 0x80; + callNext("positive "); + } + return TestSetup(&setup); } -auto setup_data_push(T)(ubyte opcode) -if (isCpu!T) + +// For DEX, DEY, DEA. +auto setup_op_DEC_reg(Reg reg) { - static if (isNMOS!T) - assert(opcode == 0x48); + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setX(cpu, 0x40); setY(cpu, 0x40); setA(cpu, 0x40); + setReg(cpu, reg, info.data); + callNext("DEC r ", [Block(0x1000, [opcode])]); + } + return connect(setup_nz(), setup_dec_data(), TestSetup(&setup)); +} + + +// For DEC. +auto setup_op_DEC() +{ + return setup_rmw(false, "DEC ", setup_dec_data()); +} + + +auto setup_inc_data() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.data = 0xFF; + callNext("zero "); + + info.data = 0x7F; + callNext("negative "); + + info.data = 0x00; + callNext("positive "); + } + return TestSetup(&setup); +} + + +// For INX, INY, INA +auto setup_op_INC_reg(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setX(cpu, 0x40); setY(cpu, 0x40); setA(cpu, 0x40); + setReg(cpu, reg, info.data); + callNext("INC r ", [Block(0x1000, [opcode])]); + } + return connect(setup_nz(), setup_inc_data(), TestSetup(&setup)); +} + + +// For INC. +auto setup_op_INC() +{ + return setup_rmw(false, "INC ", setup_inc_data()); +} + + +auto setup_rmw(bool isAcc, string name, TestSetup data_setup) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + if (isAcc) + { + setA(cpu, info.data); setX(cpu, 0x10); setY(cpu, 0x10); + callNext(name ~ "a "); + } + else + { + callNext(name, [Block(info.addr, [info.data])]); + } + } + return connect(setup_nz(), data_setup, TestSetup(&setup)); +} + + +// For ROL. +auto setup_op_ROL(bool isAcc) +{ + return setup_rmw(isAcc, "ROL ", setup_data()); +} + + +auto setup_asl_data() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.data = 0x00; + callNext("zero "); + + info.data = 0x80; + callNext("zero carry "); + + info.data = 0x40; + callNext("negative "); + + info.data = 0x01; + callNext("positive "); + } + return TestSetup(&setup); +} + + +// For ASL. +auto setup_op_ASL(bool isAcc) +{ + return setup_rmw(isAcc, "ASL ", setup_asl_data()); +} + + +auto setup_right_data() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + info.data = 0x01; + callNext("0x01 "); + + info.data = 0x80; + callNext("0x80 "); + + info.data = 0x00; + callNext("0x00 "); + } + return TestSetup(&setup); +} + + +// For ROR. +auto setup_op_ROR(bool isAcc) +{ + return setup_rmw(isAcc, "ROR ", setup_right_data()); +} + + +// For LSR. +auto setup_op_LSR(bool isAcc) +{ + return setup_rmw(isAcc, "LSR ", setup_right_data()); +} + + +// For ASO. +auto setup_op_ASO() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + callNext("acc 0x00 "); + + setA(cpu, 0xFF); + callNext("acc 0xFF "); + + setA(cpu, 0x20); + callNext("acc 0x20 "); + } + return connect(TestSetup(&setup), + setup_rmw(false, "ASO ", setup_asl_data())); +} + +// For RLA. +auto setup_op_RLA() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + callNext("acc 0x00 "); + + setA(cpu, 0xFF); + callNext("acc 0xFF "); + } + return connect(TestSetup(&setup), + setup_rmw(false, "RLA ", setup_data())); +} + + +// For LSE. +auto setup_op_LSE() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + callNext("acc 0x00 "); + + setA(cpu, 0xFF); + callNext("acc 0xFF "); + } + return connect(TestSetup(&setup), + setup_rmw(false, "LSE ", setup_right_data())); +} + + +/* + * For ADC. + * + * This opcode is extensively tested elsewhere. + */ +auto setup_op_ADC(bool cmos) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x99); + info.data = 0x01; + callNext("ADC 1", [Block(info.addr, [info.data])]); + + setA(cpu, 0x5B); + info.data = 0x46; + callNext("ADC 2", [Block(info.addr, [info.data])]); + + setA(cpu, 0x50); + info.data = 0x60; + callNext("ADC 3", [Block(info.addr, [info.data])]); + + setA(cpu, 0x90); + info.data = 0x90; + callNext("ADC 4", [Block(info.addr, [info.data])]); + + setA(cpu, 0x90); + info.data = 0x0F; + callNext("ADC 5", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(), setup_flag(Flag.C), setup_flag(Flag.D), + TestSetup(&setup)); +} + + +// For RRA. +auto setup_op_RRA() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + callNext("acc 0x00 "); + + setA(cpu, 0x10); + callNext("acc 0x10 "); + + setA(cpu, 0xBF); + callNext("acc 0xBF "); + } + + return connect(TestSetup(&setup), setup_flag(Flag.C), setup_flag(Flag.D), + setup_rmw(false, "RRA ", setup_right_data())); +} + + +/* + * For SBC. + * + * This opcode is extensively tested elsewhere. + */ +auto setup_op_SBC(bool cmos) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + info.data = 0x01; + callNext("SBC 1", [Block(info.addr, [info.data])]); + + setA(cpu, 0x46); + info.data = 0x12; + callNext("SBC 2", [Block(info.addr, [info.data])]); + + setA(cpu, 0x50); + info.data = 0x60; + callNext("SBC 3", [Block(info.addr, [info.data])]); + + setA(cpu, 0x32); + info.data = 0x02; + callNext("SBC 4", [Block(info.addr, [info.data])]); + + setA(cpu, 0x90); + info.data = 0x0F; + callNext("SBC 5", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(), setup_flag(Flag.C), setup_flag(Flag.D), + TestSetup(&setup)); +} + + +// For INS. +auto setup_op_INS() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + callNext("acc 0x00 "); + + setA(cpu, 0x10); + callNext("acc 0x10 "); + + setA(cpu, 0xBF); + callNext("acc 0xBF "); + } + + return connect(TestSetup(&setup), setup_flag(Flag.C), setup_flag(Flag.D), + setup_rmw(false, "INS ", setup_inc_data())); +} + + +// For CMP, CPY, CPX. +auto setup_op_cmp(Reg reg) +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setReg(cpu, reg, 0x10); + info.data = 0x20; + callNext("compare <", [Block(info.addr, [info.data])]); + + setReg(cpu, reg, 0x10); + info.data = 0x10; + callNext("compare =", [Block(info.addr, [info.data])]); + + setReg(cpu, reg, 0x20); + info.data = 0x10; + callNext("compare >", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(), TestSetup(&setup)); +} + + +// For DCM. +auto setup_op_DCM() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + callNext("acc 0x00 "); + + setA(cpu, 0x10); + callNext("acc 0x10 "); + + setA(cpu, 0xBF); + callNext("acc 0xBF "); + } + + return connect(TestSetup(&setup), setup_flag(Flag.C), + setup_rmw(false, "DCM ", setup_dec_data())); +} + + +// For ORA. +auto setup_op_ORA() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x00); + info.data = 0x00; + callNext("ORA zero", [Block(info.addr, [info.data])]); + + setA(cpu, 0x10); + info.data = 0x01; + callNext("ORA positive", [Block(info.addr, [info.data])]); + + setA(cpu, 0x80); + info.data = 0x00; + callNext("ORA negative", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(),TestSetup(&setup)); +} + + +// For AND. +auto setup_op_AND() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0x10); + info.data = 0x01; + callNext("AND zero", [Block(info.addr, [info.data])]); + + setA(cpu, 0x0B); + info.data = 0x0D; + callNext("AND positive", [Block(info.addr, [info.data])]); + + setA(cpu, 0x80); + info.data = 0x80; + callNext("AND negative", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(),TestSetup(&setup)); +} + + +// For EOR. +auto setup_op_EOR() +{ + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) + { + mixin testCallNext; + setA(cpu, 0xFF); + info.data = 0xFF; + callNext("EOR zero", [Block(info.addr, [info.data])]); + + setA(cpu, 0x81); + info.data = 0x82; + callNext("EOR positive", [Block(info.addr, [info.data])]); + + setA(cpu, 0x40); + info.data = 0x80; + callNext("EOR negative", [Block(info.addr, [info.data])]); + } + return connect(setup_nz(),TestSetup(&setup)); +} + + +/* + * A range over the differences in two TestMemory structs. Each + * element is an address with up to eight bytes of subsequent data + * from the two structs. + */ +struct MemDiff +{ + + static struct DiffBlock + { + ushort base; + const(ubyte[]) a; + const(ubyte[]) b; + } + + const(TestMemory*) a, b; + int i; + bool _empty, _emptyChecked; + + this(const ref TestMemory a, const ref TestMemory b) + { + assert(a.data2_base == b.data2_base && a.data2_max == b.data2_max); + this.a = &a; this.b = &b; + } + + auto front() + { + assert(_emptyChecked && !_empty); + if (i < 0x200) + { + auto extent = min(8, 0x200 - i); + return DiffBlock(cast(ushort)i, + a.data1[i..i+extent].dup, + b.data1[i..i+extent].dup); + } + else + { + auto extent = min(8, a.data2_max - i); + auto di = i - a.data2_base; + return DiffBlock(cast(ushort)i, + a.data2[di..di+extent].dup, + b.data2[di..di+extent].dup); + } + assert(0); + } + + void popFront() + { + assert(_emptyChecked && !_empty); + if (i < 0x200) + i += min(8, 0x200 - i); + else + i += min(8, a.data2_max - i); + } + + bool empty() + { + _emptyChecked = true; + while (i < 0x200) + { + if (a.data1[i] != b.data1[i]) { _empty = false; return false; } + i++; + } + if (i < a.data2_base) i = a.data2_base; + while (i < a.data2_max) + { + auto di = i - a.data2_base; + if (a.data2[di] != b.data2[di]) { _empty = false; return false; } + i++; + } + _empty = true; return true; + } +} + + +struct Expected +{ + CpuInfo cpu; + TestMemory mem; +} + +alias void delegate(ref Expected, const OpInfo) + testexpect; + + +// Expects nothing to have changed. +auto expect_none() +{ + void expect(ref Expected expected, const OpInfo info) + { + } + return &expect; +} + + +// For opcodes which modify a register and set N/Z. +void expect_basic(Reg reg, ubyte val, ref Expected expected, const OpInfo info) +{ + with(expected) + { + setReg(cpu, reg, val); + setNZ(cpu, val); + incPC(cpu, info.len); + } +} + + +// For flag opcodes. +auto expect_flag(Flag f, bool val) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + updateFlag(cpu, f, val); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For NOP. +auto expect_NOP() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) { incPC(cpu, info.len); } + } + return &expect; +} + + +// For register transfer opcodes. +auto expect_transfer(Reg source, Reg dest) +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = getReg(expected.cpu, source); + expect_basic(dest, val, expected, info); + } + return &expect; +} + + +// For push opcodes. +auto expect_push(Reg reg) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + mem[getSP(cpu)] = getReg(cpu, reg); + decSP(cpu); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For PHP. +auto expect_PHP() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + mem[getSP(cpu)] = getStatus(cpu); + decSP(cpu); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For pull opcodes. +auto expect_pull(Reg reg) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + incSP(cpu); + auto val = mem[getSP(cpu)]; + expect_basic(reg, val, expected, info); + } + } + return &expect; +} + + +// For PLP. +auto expect_PLP() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + incSP(cpu); + setStatus(cpu, mem[getSP(cpu)]); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For TSX. +auto expect_TSX() +{ + void expect(ref Expected expected, const OpInfo info) + { + ubyte val = cast(ubyte)getSP(expected.cpu); + expect_basic(Reg.X, val, expected, info); + } + return &expect; +} + + +// For TXS. +auto expect_TXS() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + auto val = getX(cpu); + setSP(cpu, val); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For load opcodes. +auto expect_load(Reg reg) +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = expected.mem[info.addr]; + expect_basic(reg, val, expected, info); + } + return &expect; +} + + +// For LAX (except immediate). +auto expect_LAX() +{ + void expect(ref Expected expected, const OpInfo info) + { + expect_load(Reg.A)(expected, info); + with(expected) { setX(cpu, getA(cpu)); } + } + return &expect; +} + +// For store opcodes. +auto expect_store(Reg reg) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + mem[info.addr] = getReg(cpu, reg); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For SAX. +auto expect_SAX() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + mem[info.addr] = getA(cpu) & getX(cpu); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For STZ. +auto expect_STZ() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + mem[info.addr] = 0; + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For branch opcodes. +auto expect_branch() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) { setPC(cpu, info.addr); } + } + return &expect; +} + + +// For BIT. +auto expect_BIT() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + updateFlag(cpu, Flag.N, (info.data & 0x80) != 0); + updateFlag(cpu, Flag.V, (info.data & 0x40) != 0); + updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For BIT. +auto expect_BIT_imm() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0); + incPC(cpu, info.len); + } + } + return &expect; +} + + +// For TRB. +auto expect_TRB() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0); + mem[info.addr] = ~getA(cpu) & info.data; + incPC(cpu, info.len); + } + } + return &expect; +} + + +auto expect_TSB() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0); + mem[info.addr] = getA(cpu) | info.data; + incPC(cpu, info.len); + } + } + return &expect; +} + +// For BRK. +auto expect_BRK() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + ushort ret = cast(ushort)(getPC(cpu) + 2); + mem[getSP(cpu)] = addrHi(ret); + decSP(cpu); + mem[getSP(cpu)] = addrLo(ret); + decSP(cpu); + mem[getSP(cpu)] = info.data; + decSP(cpu); + setPC(cpu, info.addr); + setFlag(cpu, Flag.I); + } + } + return &expect; +} + + +// For RTI. +auto expect_RTI() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + setStatus(cpu, info.data); + setPC(cpu, info.addr); + incSP(cpu, 3); + } + } + return &expect; +} + + +// For RTS. +auto expect_RTS() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + setPC(cpu, info.addr); + incSP(cpu, 2); + incPC(cpu, 1); + } + } + return &expect; +} + + +// For JMP. +auto expect_JMP() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + setPC(cpu, info.addr); + } + } + return &expect; +} + + +// For JSR. +auto expect_JSR() +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) + { + auto ret = cast(ushort)(getPC(cpu) + 2); + setPC(cpu, info.addr); + mem[getSP(cpu)] = addrHi(ret); + decSP(cpu); + mem[getSP(cpu)] = addrLo(ret); + decSP(cpu); + } + } + return &expect; +} + + +void expect_rmw(bool isAcc, ubyte val, ref Expected expected, + const OpInfo info) +{ + if (isAcc) + expect_basic(Reg.A, val, expected, info); else - assert(opcode == 0x48 || opcode == 0x5A || opcode == 0xDa); - // XXX set register to non-zero value - - return cast(datasetup_t!T[])[]; -} - -auto setup_data_pull(T)(ubyte opcode) -if (isCpu!T) -{ - assert(isNMOS!T ? opcode == 0x68 - : (opcode == 0x68 || opcode == 0x7A || opcode == 0xFA)); - - int count; - static ubyte[3] values = [0x00, 0x40, 0x80]; - static string[3] names = ["zero", "positive", "negative"]; - - auto setup(T cpu, ushort addr, out string name) { - assert(count < 3); - auto sp = pageWrapAdd(getSP(cpu), 1); - name = names[count]; - return [Block(sp, [values[count++]])]; + with(expected) + { + mem[info.addr] = val; + setNZ(cpu, val); + incPC(cpu, info.len); + } } - - return [&setup, &setup, &setup]; } -auto setup_data_op_BRK(T)(ubyte opcode) -if (isCpu!T) + +// For DEX, DEY, DEA. +auto expect_DEC_reg(Reg reg) { - assert(opcode == 0x00); - // XXX cpu flags each set/unset, with corresponding garbage values - // at sp+2 - return cast(datasetup_t!T[])[]; + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data - 1); + expect_basic(reg, val, expected, info); + } + return &expect; } -auto setup_data_op_PHP(T)(ubyte opcode) -if (isCpu!T) + +// For DEC. +auto expect_DEC() { - assert(opcode == 0x08); - // XXX cpu flags each set/unset, with corresponding garbage values - return cast(datasetup_t!T[])[]; + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data - 1); + expect_rmw(false, val, expected, info); + } + return &expect; } -auto setup_data_op_RTI(T)(ubyte opcode) -if (isCpu!T) + +// For INX, INY, INA. +auto expect_INC_reg(Reg reg) { - assert(opcode == 0x40); - // XXX values for each flag set/unset, with flags corresponding - return cast(datasetup_t!T[])[]; + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data + 1); + expect_basic(reg, val, expected, info); + } + return &expect; } -auto setup_data_op_PLP(T)(ubyte opcode) -if (isCpu!T) + +// For INC. +auto expect_INC() { - assert(opcode == 0x28); - // XXX values for each flag set/unset, with flags corresponding - return cast(datasetup_t!T[])[]; + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data + 1); + expect_rmw(false, val, expected, info); + } + return &expect; } -auto setup_data_dec_reg(T)(ubyte opcode) -if (isCpu!T) + +// For ROL. +auto expect_ROL(bool isAcc) { - /* XXX DEX, DEY, on CMOS DEC A - * set reg to: - * 0x01 (sets z) 0x00 (sets N) 0x80 (clears both) - */ + void expect(ref Expected expected, const OpInfo info) + { + auto carry = (getFlag(expected.cpu, Flag.C) ? 1 : 0); + auto val = cast(ubyte)(info.data << 1 | carry); + updateFlag(expected.cpu, Flag.C, (info.data > 0x7F)); + + expect_rmw(isAcc, val, expected, info); + } + return &expect; } -auto setup_data_dec(T)(ubyte opcode) -if (isCpu!T) + +// For ASL. +auto expect_ASL(bool isAcc) { - // XXX all addressing modes of DEC - // set addr to - // 0x01 (sets z) 0x00 (sets N) 0x80 (clears both) + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data << 1); + updateFlag(expected.cpu, Flag.C, (info.data > 0x7F)); + + expect_rmw(isAcc, val, expected, info); + } + return &expect; } -auto setup_data_inc_reg(T)(ubyte opcode) -if (isCpu!T) +// For ROR. +auto expect_ROR(bool isAcc) { - // XXX INX, INY, on CMOS INC A - // set reg to: - // 0xFF (sets Z) 0x7F (sets N) 0x00 (clears both) + void expect(ref Expected expected, const OpInfo info) + { + auto carry = (getFlag(expected.cpu, Flag.C) ? 0x80 : 0); + auto val = cast(ubyte)(info.data >> 1 | carry); + updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0)); + + expect_rmw(isAcc, val, expected, info); + } + return &expect; } -auto setup_data_inc(T)(ubyte opcode) -if (isCpu!T) + +// For LSR. +auto expect_LSR(bool isAcc) { - // XXX all addressing modes of INC - // set addr to: - // 0xFF (sets Z) 0x7F (sets N) 0x00 (clears both) + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data >> 1); + updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0)); + + expect_rmw(isAcc, val, expected, info); + } + return &expect; } -auto setup_data_rol(T)(ubyte opcode) -if (isCpu!T) + +// For ASO. +auto expect_ASO() { - // XXX all addressing modes of ROL - // if 0x2A, set A else set addr - // 0 carry set -> 1 (zero clear, carry clear, neg clear) - // 0 carry clear -> 0 (zero set, carry clear, neg clear) - // 0x80 carry set -> 1 (carry set, zero clear, neg clear) - // 0x80 carry clear -> 0 (carry set, zero set, neg clear) - // 0x40 carry set -> 0x81 (carry clear, zero clear, neg set) - // 0x40 carry clear -> 0x80 (carry clear, zero clear, neg set) + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data << 1); + updateFlag(expected.cpu, Flag.C, (info.data > 0x7F)); + setA(expected.cpu, getA(expected.cpu) | val); + + expect_rmw(false, val, expected, info); + } + return &expect; } -auto setup_data_asl(T)(ubyte opcode) -if (isCpu!T) + +// For RLA. +auto expect_RLA() { - // XXX all addressing modes of ASL - // if 0x0A, set A else set addr - // each starting carry set, carry clear - // 0 -> 0 - // 0x80 -> 0 carry set - // 0x40 -> 0x80 - // 0x01 -> 0x02 + void expect(ref Expected expected, const OpInfo info) + { + auto carry = (getFlag(expected.cpu, Flag.C) ? 1 : 0); + auto val = cast(ubyte)(info.data << 1 | carry); + updateFlag(expected.cpu, Flag.C, (info.data > 0x7F)); + setA(expected.cpu, getA(expected.cpu) & val); + + expect_rmw(false, val, expected, info); + } + return &expect; +} + + +// For LSE. +auto expect_LSE() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data >> 1); + updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0)); + setA(expected.cpu, getA(expected.cpu) ^ val); + + expect_rmw(false, val, expected, info); + } + return &expect; +} + + +/* + * Slightly cheating--this is copied from the original implementation, + * but it was independently tested (see test_decimal.d). + */ +void expect_add(bool cmos, ubyte val, ref Expected expected) +{ + with(expected) + { + ubyte acc = getA(cpu); + auto d = getFlag(cpu, Flag.D); + auto c = getFlag(cpu, Flag.C); + + if (!d) + { + uint sum = acc + val + c; + updateFlag(cpu, Flag.V, + (!((acc ^ val) & 0x80)) && ((val ^ sum) & 0x80)); + updateFlag(cpu, Flag.C, (sum > 0xFF)); + setNZ(cpu, cast(ubyte)sum); + setA(cpu, cast(ubyte)sum); + } + else + { + int a = acc; + int al = (a & 0x0F) + (val & 0x0F) + c; + if (al >= 0x0A) + al = ((al + 0x06) & 0x0F) + 0x10; + a = (a & 0xF0) + (val & 0xF0) + al; + + if (!cmos) + { + updateFlag(cpu, Flag.N, (a & 0xFF) > 0x7F); + updateFlag(cpu, Flag.Z, (cast(ubyte)(acc + val + c)) == 0); + } + updateFlag(cpu, Flag.V, + (!((acc ^ val) & 0x80)) && ((val ^ a) & 0x80)); + + if (a >= 0xA0) + a = a + 0x60; + updateFlag(cpu, Flag.C, (a >= 0x100)); + + setA(cpu, cast(ubyte)a); + if (cmos) + setNZ(cpu, cast(ubyte)a); + } + } +} + + +// For ADC. +auto expect_ADC(bool cmos) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) { incPC(cpu, info.len); } + expect_add(cmos, info.data, expected); + } + return &expect; +} + + +// For RRA. +auto expect_RRA() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto carry = (getFlag(expected.cpu, Flag.C) ? 0x80 : 0); + auto val = cast(ubyte)(info.data >> 1 | carry); + updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0)); + + expect_rmw(false, val, expected, info); + expect_add(false, val, expected); + } + return &expect; +} + + +/* + * Slightly cheating--this is copied from the original implementation, + * but it was independently tested (see test_decimal.d). + */ +void expect_sub(bool cmos, ubyte val, ref Expected expected) +{ + with(expected) + { + ubyte acc = getA(cpu); + auto d = getFlag(cpu, Flag.D); + auto c = getFlag(cpu, Flag.C); + + if (!d) + { + uint diff = acc - val - (c ? 0 : 1); + updateFlag(cpu, Flag.V, + ((acc ^ diff) & 0x80) && ((acc ^ val) & 0x80)); + updateFlag(cpu, Flag.C, (diff < 0x100)); + setA(cpu, cast(ubyte)diff); + setNZ(cpu, cast(ubyte)diff); + } + else + { + if (!cmos) + { + int a = acc; + int al = (a & 0x0F) - (val & 0x0F) - !c; + if (al < 0) + al = ((al - 0x06) & 0x0F) - 0x10; + a = (a & 0xF0) - (val & 0xF0) + al; + if (a < 0) + a = a - 0x60; + + uint diff = acc - val - !c; + updateFlag(cpu, Flag.V, + ((acc ^ diff) & 0x80) && ((acc ^ val) & 0x80)); + updateFlag(cpu, Flag.C, (diff < 0x100)); + setNZ(cpu, cast(ubyte)diff); + + setA(cpu, cast(ubyte)a); + } + else + { + int a = acc; + int al = (a & 0x0F) - (val & 0x0F) - !c; + a = a - val - !c; + if (a < 0) + a = a - 0x60; + if (al < 0) + a = a - 0x06; + + uint diff = acc - val - !c; + updateFlag(cpu, Flag.V, + ((acc ^ diff) & 0x80) && ((acc ^ val) & 0x80)); + updateFlag(cpu, Flag.C, (diff < 0x100)); + setNZ(cpu, cast(ubyte)a); + setA(cpu, cast(ubyte)a); + } + } + } +} + + +// For SBC. +auto expect_SBC(bool cmos) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) { incPC(cpu, info.len); } + expect_sub(cmos, info.data, expected); + } + return &expect; +} + + +// For INS. +auto expect_INS() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data + 1); + expect_rmw(false, val, expected, info); + expect_sub(false, val, expected); + } + return &expect; +} + + +void expect_compare(Reg reg, ubyte val, ref Expected expected) +{ + with(expected) + { + auto r = getReg(cpu, reg); + updateFlag(cpu, Flag.C, (r >= val)); + setNZ(cpu, cast(ubyte)(r - val)); + } +} + + +// For CMP, CPX, CPY. +auto expect_cmp(Reg reg) +{ + void expect(ref Expected expected, const OpInfo info) + { + with(expected) { incPC(cpu, info.len); } + expect_compare(reg, info.data, expected); + } + return &expect; +} + + +// For DCM. +auto expect_DCM() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data - 1); + expect_rmw(false, val, expected, info); + expect_compare(Reg.A, val, expected); + } + return &expect; +} + + +// For ORA. +auto expect_ORA() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data | getA(expected.cpu)); + expect_basic(Reg.A, val, expected, info); + } + return &expect; +} + + +// For AND. +auto expect_AND() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data & getA(expected.cpu)); + expect_basic(Reg.A, val, expected, info); + } + return &expect; +} + + +// For EOR. +auto expect_EOR() +{ + void expect(ref Expected expected, const OpInfo info) + { + auto val = cast(ubyte)(info.data ^ getA(expected.cpu)); + expect_basic(Reg.A, val, expected, info); + } + return &expect; } @@ -790,112 +2391,174 @@ if (isCpu!T) string getMemSetup(T)() if (isCpu!T) { - string[] tmp1 = new string[256], tmp2 = new string[256]; - tmp2[] = " setups2 = &setup_data_none!T;\n"; + string[] tmp1 = new string[256], tmp2 = new string[256], + tmp3 = new string[256]; + tmp1[] = " setup_addr = setup_none();\n"; + tmp2[] = " setup_test = setup_none();\n"; + tmp3[] = " expect = expect_none();\n"; - void call_addr(const(ubyte[]) list, string fname) + void get_addr(const(ubyte[]) list, string name, string arg = "") { foreach(op; list) - { - tmp1[op] = - " setups1 = &setup_address_" ~ fname ~ "!T;\n"; - } + tmp1[op] = "setup_addr = setup_addr_" ~ name ~ "(" ~ arg ~ ");\n"; } - void call_data(const(ubyte[]) list, string fname) + void get_test(const(ubyte[]) list, string name, string arg = "") { foreach(op; list) - { - tmp2[op] = - " setups2 = &setup_data_" ~ fname ~ "!T;\n"; - } + tmp2[op] = "setup_test = setup_op_" ~ name ~ "(" ~ arg ~ ");\n"; } + void get_expect(const(ubyte[]) list, string name, string arg = "") + { + foreach(op; list) + tmp3[op] = "expect = expect_" ~ name ~ "(" ~ arg ~ ");\n"; + } - call_addr(IMM_OPS!T, "imm"); - call_addr(ZPG_OPS!T, "zpg"); - call_addr(ZPX_OPS!T, "zpxy"); - call_addr(ZPY_OPS!T, "zpxy"); - call_addr(ABS_OPS!T, "abs"); - call_addr(ABX_OPS!T, "abxy"); - call_addr(ABY_OPS!T, "abxy"); - call_addr(IZX_OPS!T, "izx"); - call_addr(IZY_OPS!T, "izy"); - call_addr(REG_OPS!T, "reg"); - call_addr(PUSH_OPS!T, "push"); - call_addr(PULL_OPS!T, "pull"); - call_addr(BRANCH_OPS!T, "branch"); // XXX test - call_addr([0x00], "op_BRK"); - call_addr([0x20], "op_JSR"); // XXX test - call_addr([0x40, 0x60], "op_RTx"); // XXX 0x60 test - call_addr([0x4C], "op_JMP_abs"); // XXX test - call_addr([0x6C], "op_JMP_ind"); // XXX test + void get_both(const(ubyte[]) list, string name, string arg = "") + { + get_test(list, name, arg); + get_expect(list, name, arg); + } - call_data([0x08], "op_PHP"); - call_data([0x28], "op_PLP"); - call_data([0x00], "op_BRK"); - call_data([0x40], "op_RTI"); -// call_data(OPS_DEC_REG!T, "dec_reg"); -// call_data(OPS_DEC!T, "dec"); -// call_data(OPS_INC_REG!T, "inc_reg"); -// call_data(OPS_INC!T, "inc"); -// call_data(OPS_ROL!T, "rol"); -// call_data(OPS_ASL!T, "asl"); + get_addr(REG_OPS!T, "implied"); + get_addr(PUSH_OPS!T, "implied"); + get_addr(PULL_OPS!T, "implied"); + get_addr(BRANCH_OPS!T, "branch", "isCMOS!T"); + get_addr(IMM_OPS!T, "imm"); + get_addr(ZPG_OPS!T, "zpg"); + get_addr(ABS_OPS!T, "abs"); + get_addr([0x4C], "abs"); + get_addr(ZPX_OPS!T, "zpxy", "Reg.X"); + get_addr(ZPY_OPS!T, "zpxy", "Reg.Y"); + get_addr(ABX_OPS!T, "abxy", "Reg.X"); + get_addr(ABY_OPS!T, "abxy", "Reg.Y"); + get_addr(IZX_OPS!T, "izx"); + get_addr(IZY_OPS!T, "izy"); + + get_expect([0x18], "flag", "Flag.C, false"); + get_expect([0x38], "flag", "Flag.C, true"); + get_expect([0xD8], "flag", "Flag.D, false"); + get_expect([0xF8], "flag", "Flag.D, true"); + get_expect([0x58], "flag", "Flag.I, false"); + get_expect([0x78], "flag", "Flag.I, true"); + get_expect([0xB8], "flag", "Flag.V, false"); + get_both([0x08], "PHP"); + get_both([0x28], "PLP"); + get_both([0x48], "push", "Reg.A"); + get_both([0x68], "pull", "Reg.A"); + get_both([0x8A], "transfer", "Reg.X, Reg.A"); + get_both([0x98], "transfer", "Reg.Y, Reg.A"); + get_both([0xA8], "transfer", "Reg.A, Reg.Y"); + get_both([0xAA], "transfer", "Reg.A, Reg.X"); + get_test([0x9A, 0xBA], "stack_xfer"); + get_expect([0x9A], "TXS"); + get_expect([0xBA], "TSX"); + get_both([0xA1, 0xA5, 0xA9, 0xAD, 0xB1, 0xB5, 0xB9, 0xBD], + "load", "Reg.A"); + get_both([0xA2, 0xA6, 0xAE, 0xB6, 0xBE], "load", "Reg.X"); + get_both([0xA0, 0xA4, 0xAC, 0xB4, 0xBC], "load", "Reg.Y"); + get_both([0x81, 0x85, 0x8D, 0x91, 0x95, 0x99, 0x9D], "store", "Reg.A"); + get_both([0x86, 0x8E, 0x96], "store", "Reg.X"); + get_both([0x84, 0x8C, 0x94], "store", "Reg.Y"); + get_expect(BRANCH_OPS!T, "branch"); + get_both([0x24, 0x2C], "BIT"); + get_both([0x00], "BRK"); + get_both([0x40], "RTI"); + get_both([0x60], "RTS"); + get_test([0x6C], "JMP_ind", "isCMOS!T"); + get_expect([0x4C, 0x6C], "JMP"); + get_both([0x20], "JSR"); + get_both([0x88], "DEC_reg", "Reg.Y"); + get_both([0xCA], "DEC_reg", "Reg.X"); + get_both([0xC8], "INC_reg", "Reg.Y"); + get_both([0xE8], "INC_reg", "Reg.X"); + get_both([0xE6, 0xEE, 0xF6, 0xFE], "INC"); + get_both([0xC6, 0xCE, 0xD6, 0xDE], "DEC"); + get_both([0x2A], "ROL", "true"); + get_both([0x0A], "ASL", "true"); + get_both([0x6A], "ROR", "true"); + get_both([0x4A], "LSR", "true"); + get_both([0x26, 0x2E, 0x36, 0x3E], "ROL", "false"); + get_both([0x06, 0x0E, 0x16, 0x1E], "ASL", "false"); + get_both([0x66, 0x6E, 0x76, 0x7E], "ROR", "false"); + get_both([0x46, 0x4E, 0x56, 0x5E], "LSR", "false"); + get_both([0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D], + "ADC", "isCMOS!T"); + get_both([0xE1, 0xE5, 0xE9, 0xED, 0xF1, 0xF5, 0xF9, 0xFD], + "SBC", "isCMOS!T"); + get_both([0xC1, 0xC5, 0xC9, 0xCD, 0xD1, 0xD5, 0xD9, 0xDD], "cmp", "Reg.A"); + get_both([0x01, 0x05, 0x09, 0x0D, 0x11, 0x15, 0x19, 0x1D], "ORA"); + get_both([0x21, 0x25, 0x29, 0x2D, 0x31, 0x35, 0x39, 0x3D], "AND"); + get_both([0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D], "EOR"); + get_both([0xE0, 0xE4, 0xEC], "cmp", "Reg.X"); + get_both([0xC0, 0xC4, 0xCC], "cmp", "Reg.Y"); static if (isNMOS!T) { - call_addr(HLT_OPS!T, "none"); + get_addr(HLT_OPS!T, "implied"); - call_data([0x48], "push"); - call_data([0x68], "pull"); + get_test([0xA3, 0xA7, 0xAF, 0xB3, 0xB7, 0xBF], "load", "Reg.A"); + get_expect([0xA3, 0xA7, 0xAF, 0xB3, 0xB7, 0xBF], "LAX"); + get_expect([0x1A, 0x3A, 0x5A, 0x7A, 0xDA, 0xEA, 0xFA], "NOP"); + get_expect([0x0C, 0x1C, 0x3C, 0x5C, 0x7C, 0xDC, 0xFC], "NOP"); + get_expect([0x80, 0x82, 0x89, 0xC2, 0xE2], "NOP"); + get_expect([0x04, 0x44, 0x64], "NOP"); + get_expect([0x14, 0x34, 0x54, 0x74, 0xD4, 0xF4], "NOP"); + get_both([0x83, 0x87, 0x8F, 0x97], "SAX"); + get_both([0x03, 0x07, 0x0F, 0x13, 0x17, 0x1B, 0x1F], "ASO"); + get_both([0x23, 0x27, 0x2F, 0x33, 0x37, 0x3B, 0x3F], "RLA"); + get_both([0x43, 0x47, 0x4F, 0x53, 0x57, 0x5B, 0x5F], "LSE"); + get_both([0x63, 0x67, 0x6F, 0x73, 0x77, 0x7B, 0x7F], "RRA"); + get_both([0xE3, 0xE7, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF], "INS"); + get_both([0xC3, 0xC7, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF], "DCM"); + get_both([0xEB], "SBC", "false"); + + // TODO: implement these opcode tests + get_test([0x0B, 0x2B, 0x4B, 0x6B, 0x8B, 0x93, 0x9B, 0x9C, 0x9E, + 0x9F, 0xAB, 0xBB, 0xCB, 0xEB], "abort"); } else { - call_addr(ZPI_OPS!T, "zpi"); - call_addr(NOP1_OPS!T, "reg"); /// XXX nop test 1 cycles - call_addr([0x5C], "op_5C"); /// XXX test - call_addr([0x7C], "op_JMP_inx"); /// XXX test - - call_data([0x48, 0x5A, 0xDA], "push"); - call_data([0x68, 0x7A, 0xFA], "pull"); + get_addr(NOP1_OPS!T, "implied"); + get_addr(ZPI_OPS!T, "zpi"); + get_both([0x12], "ORA"); + get_both([0x32], "AND"); + get_both([0x52], "EOR"); + get_both([0x72], "ADC", "true"); + get_both([0x92], "store", "Reg.A"); + get_both([0xB2], "load", "Reg.A"); + get_both([0xD2], "cmp", "Reg.A"); + get_both([0xF2], "SBC", "true"); + get_both([0x5A], "push", "Reg.Y"); + get_both([0xDA], "push", "Reg.X"); + get_both([0x7A], "pull", "Reg.Y"); + get_both([0xFA], "pull", "Reg.X"); + get_expect([0xEA], "NOP"); + get_expect(NOP1_OPS!T, "NOP"); + get_expect([0x02, 0x22, 0x42, 0x62, 0x82, 0xC2, 0xE2], "NOP"); + get_expect([0x44, 0x54, 0xD4, 0xF4, 0xDC, 0xFC], "NOP"); + get_both([0x64, 0x74, 0x9C, 0x9E], "STZ"); + get_both([0x34, 0x3C], "BIT"); + get_test([0x89], "BIT"); get_expect([0x89], "BIT_imm"); + get_both([0x14, 0x1C], "TRB"); + get_both([0x04, 0x0C], "TSB"); + get_test([0x7C], "JMP_inx"); + get_expect([0x7C], "JMP"); + get_both([0xB2], "load", "Reg.A"); + get_both([0x92], "store", "Reg.A"); + get_test([0x5C], "5C"); + get_expect([0x5C], "NOP"); + get_both([0x3A], "DEC_reg", "Reg.A"); + get_both([0x1A], "INC_reg", "Reg.A"); } auto ret = "final switch (opcode)\n{\n"; for (auto i = 0; i < 256; i++) { ret ~= " case 0x" ~ to!string(i, 16) ~ ":\n" ~ - tmp1[i] ~ tmp2[i] ~ " break;\n"; + " " ~ tmp1[i] ~ " " ~ tmp2[i] ~ + " " ~ tmp3[i] ~ " break;\n"; } return ret ~ "\n}"; } - - -unittest -{ -/+ - import std.stdio; - alias Cmos!(false, false) T; - addrsetup_t!T[] function(ubyte) setups1; - datasetup_t!T[] function(ubyte) setups2; - ubyte opcode = 0x10; - mixin(getMemSetup!T()); - - auto funcs1 = setups1(opcode); - string name1, name2; - foreach(func1; funcs1) - { - auto funcs2 = setups2(opcode); - foreach(func2; funcs2) - { - ushort addr; - auto cpu = new T(); - auto block1 = func1(cpu, addr, name1); - auto block2 = func2(cpu, addr, name2); - auto mem = TestMemory(block1 ~ block2); - connectCpu(cpu, mem); - } - } -+/ -// enum foo = getMemSetup!(NmosUndoc!(false, false))(); -// writeln(foo); -} diff --git a/test/cpu.d b/test/cpu.d index 9df226d..796c446 100644 --- a/test/cpu.d +++ b/test/cpu.d @@ -58,6 +58,19 @@ template CPU(string arch, bool strict, bool cumulative) } +auto makeCpu(T)(CpuInfo info) +if (isCpu!T) +{ + auto cpu = new T(); + cpu.programCounter = info.PC; + cpu.stackPointer = info.SP; + cpu.flag.fromByte(info.S); + cpu.accumulator = info.A; + cpu.xIndex = info.X; + cpu.yIndex = info.Y; + return cpu; +} + // Connects test memory to a cpu. void connectMem(T, S)(T cpu, ref S mem) if (isCpu!T) @@ -222,6 +235,33 @@ if (isCpu!T) } +struct CpuInfo +{ + ushort PC; + ubyte SP = 0xFF; + ubyte A, X, Y; + ubyte S = 0x30; + + string toString() const + { + return format("PC %0.4X SP %0.2X S %0.2X A %0.2X X %0.2X Y %0.2X", + PC, SP, S, A, X, Y); + } + + static CpuInfo fromCpu(T)(T cpu) + { + CpuInfo info; + info.PC = cpu.programCounter; + info.SP = cpu.stackPointer; + info.A = cpu.accumulator; + info.X = cpu.xIndex; + info.Y = cpu.yIndex; + info.S = cpu.flag.toByte(); + return info; + } +} + + // Sets the program counter. void setPC(T)(T cpu, int addr) if (isCpu!T) @@ -229,6 +269,16 @@ if (isCpu!T) cpu.programCounter = cast(ushort)addr; } +void setPC(T : CpuInfo)(ref T cpu, int addr) +{ + cpu.PC = cast(ushort)addr; +} + +void incPC(T : CpuInfo)(ref T cpu, int amt = 1) +{ + cpu.PC = pageCrossAdd(cpu.PC, amt); +} + // Returns the program counter. ushort getPC(T)(T cpu) if (isCpu!T) @@ -236,6 +286,11 @@ if (isCpu!T) return cpu.programCounter; } +ushort getPC(T : CpuInfo)(ref T cpu) +{ + return cpu.PC; +} + /* * Sets the stack pointer. @@ -250,6 +305,23 @@ if (isCpu!T) cpu.stackPointer = cast(ubyte)val; } +void setSP(T : CpuInfo)(ref T cpu, int val) +{ + assert(val < 0x0200); + cpu.SP = cast(ubyte)val; +} + +void incSP(T : CpuInfo)(ref T cpu, int amt = 1) +{ + cpu.SP = cast(ubyte)pageWrapAdd(cpu.SP, amt); +} + +void decSP(T : CpuInfo)(ref T cpu, int amt = -1) +{ + cpu.SP = cast(ubyte)pageWrapAdd(cpu.SP, amt); +} + + /* * Returns the stack address (in the range 0x0100-0x01FF) represented * by the stack pointer. @@ -260,6 +332,66 @@ if (isCpu!T) return 0x100 | cpu.stackPointer; } +ushort getSP(T : CpuInfo)(ref T cpu) +{ + return 0x0100 | cpu.SP; +} + + +// The names of the registers. +enum Reg +{ + A, X, Y +} + +// Sets a register. +void setReg(T)(ref T cpu, Reg reg, int val) +if (isCpu!T || is(T == CpuInfo)) +{ + final switch (reg) + { + case Reg.A: setA(cpu, val); break; + case Reg.X: setX(cpu, val); break; + case Reg.Y: setY(cpu, val); break; + } +} + +// Returns a register +ubyte getReg(T)(ref T cpu, Reg reg) +if (isCpu!T || is(T == CpuInfo)) +{ + final switch (reg) + { + case Reg.A: return getA(cpu); + case Reg.X: return getX(cpu); + case Reg.Y: return getY(cpu); + } +} + +// Sets the A register. +void setA(T)(T cpu, int val) +if (isCpu!T) +{ + cpu.accumulator = cast(ubyte)val; +} + +void setA(T : CpuInfo)(ref T cpu, int val) +{ + cpu.A = cast(ubyte)val; +} + +// Returns the A register. +ubyte getA(T)(T cpu, int val) +if (isCpu!T) +{ + return cpu.accumulator; +} + +ubyte getA(T : CpuInfo)(ref T cpu) +{ + return cpu.A; +} + // Sets the X register. void setX(T)(T cpu, int val) @@ -268,6 +400,11 @@ if (isCpu!T) cpu.xIndex = cast(ubyte)val; } +void setX(T : CpuInfo)(ref T cpu, int val) +{ + cpu.X = cast(ubyte)val; +} + // Returns the X register. ubyte getX(T)(T cpu) if (isCpu!T) @@ -275,6 +412,11 @@ if (isCpu!T) return cpu.xIndex; } +ubyte getX(T : CpuInfo)(ref T cpu) +{ + return cpu.X; +} + // Sets the Y register. void setY(T)(T cpu, int val) @@ -283,6 +425,11 @@ if (isCpu!T) cpu.yIndex = cast(ubyte)val; } +void setY(T : CpuInfo)(ref T cpu, int val) +{ + cpu.Y = cast(ubyte)val; +} + // Returns the Y register. ubyte getY(T)(T cpu) if (isCpu!T) @@ -290,6 +437,11 @@ if (isCpu!T) return cpu.yIndex; } +ubyte getY(T : CpuInfo)(ref T cpu) +{ + return cpu.Y; +} + // The names of the status flags. enum Flag : ubyte @@ -302,6 +454,20 @@ enum Flag : ubyte N = 0x80 } +string flagToString(Flag f) +{ + switch (f) + { + case Flag.C: return "C"; + case Flag.Z: return "Z"; + case Flag.I: return "I"; + case Flag.D: return "D"; + case Flag.V: return "V"; + case Flag.N: return "N"; + default: return "?"; + } +} + // Sets one or more status flags. void setFlag(T)(T cpu, Flag[] flags...) if (isCpu!T) @@ -311,6 +477,11 @@ if (isCpu!T) cpu.flag.fromByte(reg); } +void setFlag(T : CpuInfo)(ref T cpu, Flag[] flags...) +{ + foreach (flag; flags) cpu.S |= flag; +} + // Clears one or more status flags. void clearFlag(T)(T cpu, Flag[] flags...) if (isCpu!T) @@ -320,6 +491,11 @@ if (isCpu!T) cpu.flag.fromByte(reg); } +void clearFlag(T : CpuInfo)(ref T cpu, Flag[] flags...) +{ + foreach (flag; flags) cpu.S &= ~flag; +} + // Returns a status flag. bool getFlag(T)(T cpu, Flag f) if (isCpu!T) @@ -327,9 +503,39 @@ if (isCpu!T) return (cpu.flag.toByte() & f) != 0; } -// Sets or clears a single status flag. -void updateFlag(T)(T cpu, Flag f, bool val) +bool getFlag(T : CpuInfo)(ref T cpu, Flag f) +{ + return (cpu.S & f) != 0; +} + + +// Sets the status register from a byte. +void setStatus(T)(T cpu, int val) if (isCpu!T) +{ + cpu.flag.fromByte(cast(ubyte)val); +} + +void setStatus(T : CpuInfo)(ref T cpu, int val) +{ + cpu.S = cast(ubyte)val | 0x30; +} + +// Returns the status register as a byte. +ubyte getStatus(T)(T cpu) +if (isCpu!T) +{ + return cpu.flag.toByte(); +} + +ubyte getStatus(T : CpuInfo)(ref T cpu) +{ + return cpu.S | 0x30; +} + +// Sets or clears a single status flag. +void updateFlag(T)(ref T cpu, Flag f, bool val) +if (isCpu!T || is(T == CpuInfo)) { if (val) setFlag(cpu, f); @@ -337,10 +543,15 @@ if (isCpu!T) clearFlag(cpu, f); } +void setNZ(T : CpuInfo)(ref T cpu, ubyte val) +{ + updateFlag(cpu, Flag.Z, (val == 0)); + updateFlag(cpu, Flag.N, (val >= 0x80)); +} // Sets or clears the flag required for a given opcode to branch. -void expectBranch(T)(T cpu, ubyte opcode) -if (isCpu!T) +void expectBranch(T)(ref T cpu, ubyte opcode) +if (isCpu!T || is(T == CpuInfo)) { switch (opcode) { @@ -348,19 +559,19 @@ if (isCpu!T) case /*BMI*/ 0x30: setFlag(cpu, Flag.N); break; case /*BVC*/ 0x50: clearFlag(cpu, Flag.V); break; case /*BVS*/ 0x70: setFlag(cpu, Flag.V); break; + case /*BRA*/ 0x80: break; case /*BCC*/ 0x90: clearFlag(cpu, Flag.C); break; case /*BCS*/ 0xB0: setFlag(cpu, Flag.C); break; case /*BNE*/ 0xD0: clearFlag(cpu, Flag.Z); break; case /*BEQ*/ 0xF0: setFlag(cpu, Flag.Z); break; default: - if (isCMOS!T) { if (opcode == /*BRA*/ 0x80) break; } enforce(0, format("not a branching opcpde %0.2X", opcode)); } } // Returns whether an opcode would branch if executed. -bool wouldBranch(T)(T cpu, ubyte opcode) -if (isCpu!T) +bool wouldBranch(T)(ref T cpu, ubyte opcode) +if (isCpu!T || is(T == CpuInfo)) { switch (opcode) { @@ -368,18 +579,19 @@ if (isCpu!T) case /*BMI*/ 0x30: return getFlag(cpu, Flag.N); case /*BVC*/ 0x50: return !getFlag(cpu, Flag.V); case /*BVS*/ 0x70: return getFlag(cpu, Flag.V); + case /*BRA*/ 0x80: return true; case /*BCC*/ 0x90: return !getFlag(cpu, Flag.C); case /*BCS*/ 0xB0: return getFlag(cpu, Flag.C); case /*BNE*/ 0xD0: return !getFlag(cpu, Flag.Z); case /*BEQ*/ 0xF0: return getFlag(cpu, Flag.Z); default: - if (isCMOS!T) { if (opcode == /*BRA*/ 0x80) return true; } assert(0, format("not a branching opcpde %0.2X", opcode)); } } // Sets or clears the flag required for a given opcode to not branch. -void expectNoBranch(T)(T cpu, ubyte opcode) +void expectNoBranch(T)(ref T cpu, ubyte opcode) +if (isCpu!T || is(T == CpuInfo)) { switch (opcode) { @@ -406,6 +618,17 @@ ushort address(ubyte l, ubyte h) } +ubyte addrLo(ushort addr) +{ + return cast(ubyte)(addr & 0xFF); +} + +ubyte addrHi(ushort addr) +{ + return cast(ubyte)(addr >> 8); +} + + /* * Adds an offset to an address, resulting in an address in the same * page. @@ -435,7 +658,7 @@ ubyte XX() // A number different from some other number. ubyte notXX(ubyte val) { - return cast(ubyte)(val ^ 0xAA); + return cast(ubyte)~val; } diff --git a/test/opcodes.d b/test/opcodes.d index 6df40b5..f07fcda 100644 --- a/test/opcodes.d +++ b/test/opcodes.d @@ -10,12 +10,12 @@ if (isCpu!T) { static if (isNMOS!T) enum REG_OPS = cast(ubyte[]) - x"0A 18 1A 2A 38 3A 4A 58 5A 6A 78 7A 8A 88 98 9A + x"0A 18 1A 2A 38 3A 4A 58 5A 6A 78 7A 88 8A 98 9A A8 AA B8 BA C8 CA D8 DA E8 EA F8 FA"; else enum REG_OPS = cast(ubyte[]) - x"0A 18 1A 2A 38 3A 4A 58 6A 78 8A 88 98 9A - A8 AA B8 BA C8 CA D8 E8 EA F8"; + x"0A 18 1A 2A 38 3A 4A 58 6A 78 88 8A 98 9A A8 AA + B8 BA C8 CA D8 E8 EA F8"; } diff --git a/test/test_bus.d b/test/test_bus.d index a05bc9f..9a1008b 100644 --- a/test/test_bus.d +++ b/test/test_bus.d @@ -77,14 +77,14 @@ if (isCpu!T) auto opcode = mem[pc]; assert(IMM_OPS!T.canFind(opcode)); - bool decimal = isCMOS!T && isStrict!T && getFlag(cpu, Flag.D) && + 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( - [Bus(Action.READ, pc+2)]); + If!decimal(If!(isStrict!T)( + [Bus(Action.READ, pc+2)])); } @@ -287,6 +287,7 @@ if (isCpu!T && isNMOS!T) auto opcode = mem[pc]; assert(HLT_OPS!T.canFind(opcode)); + cycles = 1; return [Bus(Action.READ, pc)]; } @@ -346,14 +347,14 @@ if (isCpu!T) bool rmw = RMW_OPS!T.canFind(opcode); bool write = !rmw && WRITE_OPS!T.canFind(opcode); bool read = !rmw && !write; - bool decimal = isCMOS!T && isStrict!T && getFlag(cpu, Flag.D) && + 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( - [Bus(Action.READ, pc + opLen)])) ~ + If!decimal(If!(isStrict!T)( + [Bus(Action.READ, pc + opLen)]))) ~ If!write( [Bus(Action.WRITE, addr)]) ~ If!rmw( @@ -612,93 +613,116 @@ template timesetup_t(T) } -// Tests the bus access patterns and cycles taken for a given opcode. -void test_opcode_timing(T)(ubyte opcode) +alias void delegate(int, const Bus[], int, const Bus[], ubyte, string) + busreport; + + +auto run_timing_test(T)(timesetup_t!T expect, busreport report) { - addrsetup_t!T[] function(ubyte) setups1; - datasetup_t!T[] function(ubyte) setups2; - mixin(getMemSetup!T()); - - timesetup_t!T expected; - mixin(getExpected!T()); - - auto funcs1 = setups1(opcode); - string name1; - foreach(func1; funcs1) + auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info, + string msg, TestSetup* next) { - ushort addr; - int cycles; - auto cpu = new T(); - auto block1 = func1(cpu, addr, name1); - auto mem = TestMemory(block1); - connectMem(cpu, mem); - auto exp = expected(cpu, mem, cycles); - exp = exp ~ new Bus[8 - exp.length]; - auto actual = recordBus(cpu); - auto actualCycles = recordCycles(cpu); - // XXX debug - write(format("Testing %s (%0.2X) -- ", name1, opcode)); - try - { - runOneOpcode(cpu); - } - catch (TestException e) // possibly not related to timing - { - // XXX wrap - throw e; - } - if (actual != exp) + 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 != cycles) + if (actualCycles != expectCycles) { // XXX make error message, throw } - if (actual == exp && actualCycles == cycles) - writeln("OK"); + if (actualBus == expectBus && actualCycles == expectCycles) {} else { + write(format("[%0.2X] %s", opcode, msg)); writeln(); - writeln(actualCycles, " ", cycles); - writeln(actual); - writeln(exp); + 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(); + alias CPU!("65C02", false, false) T1; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T1(cast(ubyte)op); + test_opcode_timing!T1(cast(ubyte)op, report); alias CPU!("65C02", true, false) T2; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T2(cast(ubyte)op); + test_opcode_timing!T2(cast(ubyte)op, report); alias CPU!("6502", false, false) T3; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T3(cast(ubyte)op); + test_opcode_timing!T3(cast(ubyte)op, report); alias CPU!("6502", true, false) T4; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T4(cast(ubyte)op); + test_opcode_timing!T4(cast(ubyte)op, report); alias CPU!("65C02", false, true) T5; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T5(cast(ubyte)op); + test_opcode_timing!T5(cast(ubyte)op, report); alias CPU!("65C02", true, true) T6; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T6(cast(ubyte)op); + test_opcode_timing!T6(cast(ubyte)op, report); alias CPU!("6502", false, true) T7; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T7(cast(ubyte)op); + test_opcode_timing!T7(cast(ubyte)op, report); alias CPU!("6502", true, true) T8; for (int op = 0x00; op < 0x100; op++) - test_opcode_timing!T8(cast(ubyte)op); + test_opcode_timing!T8(cast(ubyte)op, report); } diff --git a/test/test_func.d b/test/test_func.d new file mode 100644 index 0000000..1743c7a --- /dev/null +++ b/test/test_func.d @@ -0,0 +1,104 @@ +module test.test_func; + + +import std.string; + +import test.base, test.cpu, test.opcodes; + + +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 +{ + auto report = report_debug(); + + alias CPU!("65C02", false, false) T1; + foreach (opcode; 0..255) + test_one_opcode!T1(cast(ubyte)opcode, report); + + alias CPU!("6502", false, false) T2; + foreach (opcode; 0..255) + test_one_opcode!T2(cast(ubyte)opcode, report); +}