diff --git a/test/base.d b/test/base.d index 51076fb..f7e75a9 100644 --- a/test/base.d +++ b/test/base.d @@ -1,88 +1,65 @@ module test.base; -import std.conv, std.exception, std.string, std.traits; +import std.algorithm, std.conv, std.exception, std.random, std.range, + std.string, std.traits; - -public import d6502.nmosundoc : NmosUndoc; -public import d6502.cmos : Cmos; - - -template isNMOS(T) -{ - enum isNMOS = __traits(hasMember, T, "_isNMOS"); -} - -template isCMOS(T) -{ - enum isCMOS = __traits(hasMember, T, "_isCMOS"); -} - -template isCpu(T) -{ - enum isCpu = __traits(hasMember, T, "_isCpuBase"); -} - -template isStrict(T) -{ - enum isStrict = __traits(hasMember, T, "_isStrict"); -} - -template isCumulative(T) -{ - enum isCumulative = __traits(hasMember, T, "_isCumulative"); -} +public import test.wrap6502; class TestException : Exception { this(string msg) { super(msg); } } -struct Block -{ - ushort base; - ubyte[] data; -} - -/* Emulates zero page, stack, and 3 additional pages starting at a - * user-defined address. Accesses outside the defined address space - * raise an exception. +/* + * 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. */ struct TestMemory { +private: ubyte[0x200] data1; ubyte[0x300] data2; immutable ushort data2_base; immutable size_t data2_max; - this(Block[] blocks ...) +public: + /* + * Constructs a TestMemory with data filled in from an array of + * Blocks. + * + * The blocks do not need to be contiguous, or ordered by their + * base address, but note that the base address of the 3-page + * "main memory" will be that of the first block with a base + * address greater than 0x01FF (there must be at least one such + * block). + */ + this(const Block[] blocks ...) { - size_t last_1, last_2; foreach (block; blocks) { auto base = block.base; auto data = block.data; if (base < 0x200) { - enforce(base >= last_1, - format("Overlapping address %0.4x", base)); enforce(base + data.length <= 0x200, format("Address out of bounds %0.4x", base)); - last_1 = base + data.length; - data1[base..last_1] = data[]; + data1[base..base + data.length] = data[]; } else { if (!data2_base) { - last_2 = data2_base = base; + if (base > 0xFD00) + data2_base = 0xFD00; + else + data2_base = base; data2_max = base + 0x300; } - enforce(base >= last_2, - format("Overlapping address %0.4x", base)); enforce(base + data.length <= data2_max, format("Address out of bounds %0.4x", base)); - last_2 = base + data.length; - data2[base-data2_base..last_2-data2_base] = data[]; + auto last = base + data.length; + data2[base-data2_base..last-data2_base] = data[]; } } enforce(data2_base, "Missing memory > 0x0200"); @@ -117,18 +94,28 @@ struct TestMemory } } - -T makeCpu(T)(ref TestMemory mem) -if (isCpu!T) +/* + * A block of memory with a given base address. + */ +struct Block { - static if (isCumulative!T) void tick(int cycles) {} - else void tick() {} + ushort base; + ubyte[] data; - auto cpu = new T(); - cpu.memoryRead = &mem.read; - cpu.memoryWrite = &mem.write; - cpu.tick = &tick; - return cpu; + 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 ~ "]"; + } } @@ -149,107 +136,1099 @@ if (isPointer!T) return Ref!(const(T))(ptr); } -auto recordCycles(T)(T cpu) + +enum Flag : ubyte +{ + C = 0x01, + Z = 0x02, + I = 0x04, + D = 0x08, + V = 0x40, + N = 0x80 +} + +void updateFlag(T)(T cpu, Flag f, bool val) if (isCpu!T) { - auto cycles = new int; - auto wrappedTick = cpu.tick; + if (val) + setFlag(cpu, f); + else + clearFlag(cpu, f); +} - static if (isCumulative!T) + +void expectBranch(T)(T cpu, ubyte opcode) +if (isCpu!T) +{ + switch (opcode) { - void tick(int cyc) + case /*BPL*/ 0x10: clearFlag(cpu, Flag.N); break; + 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 /*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)); + } +} + +bool wouldBranch(T)(T cpu, ubyte opcode) +if (isCpu!T) +{ + switch (opcode) + { + case /*BPL*/ 0x10: return !getFlag(cpu, Flag.N); + 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 /*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)); + } +} + +void expectNoBranch(T)(T cpu, ubyte opcode) +{ + switch (opcode) + { + case /*BPL*/ 0x10: setFlag(cpu, Flag.N); break; + case /*BMI*/ 0x30: clearFlag(cpu, Flag.N); break; + case /*BVC*/ 0x50: setFlag(cpu, Flag.V); break; + case /*BVS*/ 0x70: clearFlag(cpu, Flag.V); break; + case /*BCC*/ 0x90: setFlag(cpu, Flag.C); break; + case /*BCS*/ 0xB0: clearFlag(cpu, Flag.C); break; + case /*BNE*/ 0xD0: setFlag(cpu, Flag.Z); break; + case /*BEQ*/ 0xF0: clearFlag(cpu, Flag.Z); break; + default: + if (isCMOS!T) + enforce(opcode != 0x80, "BRA can never not branch"); + enforce(0, format("not a branching opcpde %0.2X", opcode)); + } +} + + +ushort address(ubyte l, ubyte h) +{ + return cast(ushort)((h << 8) | l); +} + +ushort pageWrapAdd(ushort base, int offset) +{ + return (base & 0xFF00) + cast(ubyte)((base & 0xFF) + offset); +} + +ushort pageCrossAdd(ushort base, int offset) +{ + return cast(ushort)(base + offset); +} + + +// A random value to use for "uninitialized" memory. +ubyte XX() +{ + return cast(ubyte)uniform(0, 256); +} + +// A number different from some other number. +ubyte notXX(ubyte val) +{ + return cast(ubyte)(val ^ 0xAA); +} + + +// 2-cycle opcodes which neither read nor write. +template REG_OPS(T) +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 + 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"; +} + + +// Opcodes which push to the stack. +template PUSH_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum PUSH_OPS = cast(ubyte[])x"08 48"; + else + enum PUSH_OPS = cast(ubyte[])x"08 48 5A DA"; +} + + +// Opcodes which pull from the stack. +template PULL_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum PULL_OPS = cast(ubyte[])x"28 68"; + else + enum PULL_OPS = cast(ubyte[])x"28 68 7A FA"; +} + + +// Relative branch opcodes. +template BRANCH_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum BRANCH_OPS = cast(ubyte[])x"10 30 50 70 90 B0 D0 F0"; + else + enum BRANCH_OPS = cast(ubyte[])x"10 30 50 70 80 90 B0 D0 F0"; +} + + +// Write-only opcodes. +template WRITE_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum WRITE_OPS = cast(ubyte[])x"81 83 84 85 86 87 8C 8D 8E 8F + 91 93 94 95 96 97 99 9B 9C 9D 9E 9F"; + else + enum WRITE_OPS = cast(ubyte[])x"64 74 81 84 85 86 8C 8D 8E + 91 92 94 95 96 99 9C 9D 9E"; +} + + +// Read-only opcodes (excluding ADC/SBC). +template READ_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum READ_OPS = cast(ubyte[]) + x"01 04 05 09 0B 0C 0D 11 14 15 19 1C 1D + 21 24 25 29 2B 2C 2D 31 34 35 39 3C 3D + 41 44 45 49 4B 4D 51 54 55 59 5C 5D + 64 6B 74 7C 82 89 8B + A0 A1 A2 A3 A4 A5 A6 A7 A9 AB AC AD AE AF + B1 B3 B4 B5 B6 B7 B9 BB BC BD BE BF + C0 C1 C2 C4 C5 C9 CB CC CD D1 D4 D5 D9 DC DD + E0 E2 E4 EC F4 FC"; + else + enum READ_OPS = cast(ubyte[]) + x"01 02 05 09 0D 11 12 15 19 1D + 21 22 24 25 29 2C 2D 31 32 34 35 39 3C 3D + 41 42 44 45 49 4D 51 52 54 55 59 5D 62 82 89 + A0 A1 A2 A4 A5 A6 A9 AC AD AE + B2 B1 B4 B5 B6 B9 BC BD BE + C0 C1 C2 C4 C5 C9 CC CD D1 D2 D4 D5 D9 DC DD + E0 E2 E4 EC F4 FC"; +} + + +// Opcodes affected by decimal mode (ADC/SBC). +template BCD_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum BCD_OPS = cast(ubyte[])x"61 65 69 6B 6D 71 75 79 7D + E1 E5 E9 EB ED F1 F5 F9 FD"; + else + enum BCD_OPS = cast(ubyte[])x"61 65 69 6D 71 72 75 79 7D + E1 E5 E9 ED F1 F2 F5 F9 FD"; +} + + +// Opcodes which both read and write. +template RMW_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum RMW_OPS = cast(ubyte[]) + x"03 06 07 0E 0F 13 16 17 1B 1E 1F + 23 26 27 2E 2F 33 36 37 3B 3E 3F + 43 46 47 4E 4F 53 56 57 5B 5E 5F + 63 66 67 6E 6F 73 76 77 7B 7E 7F + C3 C6 C7 CE CF D3 D6 D7 DB DE DF + E3 E6 E7 EE EF F3 F6 F7 FB FE FF"; + else + enum RMW_OPS = cast(ubyte[]) + x"04 06 0C 0E 14 16 1C 1E 26 2E 36 3E 46 4E 56 5E + 66 6E 76 7E C6 CE D6 DE E6 EE F6 FE"; +} + + +// Opcodes with immediate address mode. +template IMM_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum IMM_OPS = cast(ubyte[])x"09 0B 29 2B 49 4B 69 6B + 80 82 89 8B A0 A2 A9 AB + C0 C2 C9 CB E0 E2 E9 EB"; + else + enum IMM_OPS = cast(ubyte[])x"02 09 22 29 42 49 62 69 82 + 89 A0 A2 A9 C0 C2 C9 E0 E2 E9"; +} + + +// Opcodes with zeropage address mode. +template ZPG_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum ZPG_OPS = cast(ubyte[])x"04 05 06 07 24 25 26 27 + 44 45 46 47 64 65 66 67 + 84 85 86 87 A4 A5 A6 A7 + C4 C5 C6 C7 E4 E5 E6 E7"; + else + enum ZPG_OPS = cast(ubyte[])x"04 05 06 14 24 25 26 44 45 46 64 65 66 + 84 85 86 A4 A5 A6 C4 C5 C6 E4 E5 E6"; +} + + +// Opcodes with zeropage,x address mode. +template ZPX_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum ZPX_OPS = cast(ubyte[])x"14 15 16 17 34 35 36 37 + 54 55 56 57 74 75 76 77 + 94 95 B4 B5 D4 D5 D6 D7 + F4 F5 F6 F7"; + else + enum ZPX_OPS = cast(ubyte[])x"15 16 34 35 36 54 55 56 74 75 76 + 94 95 B4 B5 D4 D5 D6 F4 F5 F6"; +} + + +// Opcodes with zeropage,y address mode. +template ZPY_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum ZPY_OPS = cast(ubyte[])x"96 97 B6 B7"; + else + enum ZPY_OPS = cast(ubyte[])x"96 B6"; +} + + +// Opcodes with absolute address mode. +template ABS_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum ABS_OPS = cast(ubyte[])x"0C 0D 0E 0F 2C 2D 2E 2F + 4C 4D 4E 4F 6D 6E 6F + 8C 8D 8E 8F AC AD AE AF + CC CD CE CF EC ED EE EF"; + else + enum ABS_OPS = cast(ubyte[])x"0C 0D 0E 1C 2C 2D 2E 4C 4D 4E 6D 6E + 8C 8D 8E 9C AC AD AE CC CD CE EC ED EE"; +} + + +// Opcodes with absolute,x address mode. +template ABX_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum ABX_OPS = cast(ubyte[])x"1C 1D 1E 1F 3C 3D 3E 3F + 5C 5D 5E 5F 7C 7D 7E 7F + 9C 9D BC BD DC DD DE DF + FC FD FE FF"; + else + enum ABX_OPS = cast(ubyte[])x"1D 1E 3C 3D 3E 5D 5E 7D 7E + 9D 9E BC BD DC DD DE FC FD FE"; +} + + +// Opcodes with absolute,y address mode. +template ABY_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum ABY_OPS = cast(ubyte[])x"19 1B 39 3B 59 5B 79 7B + 99 9B 9E 9F B9 BB BE BF + D9 DB F9 FB"; + else + enum ABY_OPS = cast(ubyte[])x"19 39 59 79 99 B9 BE D9 F9"; +} + + +// Opcodes with indirect zeropage,x address mode. +template IZX_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum IZX_OPS = cast(ubyte[])x"01 03 21 23 41 43 61 63 + 81 83 A1 A3 C1 C3 E1 E3"; + else + enum IZX_OPS = cast(ubyte[])x"01 21 41 61 81 A1 C1 E1"; +} + + +// Opcodes with indirect zeropage,y address mode. +template IZY_OPS(T) +if (isCpu!T) +{ + static if (isNMOS!T) + enum IZY_OPS = cast(ubyte[])x"11 13 31 33 51 53 71 73 + 91 93 B1 B3 D1 D3 F1 F3"; + else + enum IZY_OPS = cast(ubyte[])x"11 31 51 71 91 B1 D1 F1"; +} + + +// Opcodes with indirect zeropage address mode. +template ZPI_OPS(T) +if (isCpu!T && isCMOS!T) +{ + enum ZPI_OPS = cast(ubyte[])x"12 32 52 72 92 B2 D2 F2"; +} + + +// 1-cycle NOPS. +template NOP1_OPS(T) +if (isCpu!T && isCMOS!T) +{ + enum NOP1_OPS = cast(ubyte[]) + x"03 13 23 33 43 53 63 73 83 93 A3 B3 C3 D3 E3 F3 + 07 17 27 37 47 57 67 77 87 97 A7 B7 C7 D7 E7 F7 + 0B 1B 2B 3B 4B 5B 6B 7B 8B 9B AB BB CB DB EB FB + 0F 1F 2F 3F 4F 5F 6F 7F 8F 9F AF BF CF DF EF FF"; +} + + +// NMOS HLT opcodes. +template HLT_OPS(T) +if (isCpu!T && isNMOS!T) +{ + enum HLT_OPS = cast(ubyte[])x"02 12 22 32 42 52 62 72 92 B2 D2 F2"; +} + + +// Associates opcodes with test setup functions. +string getMemSetup(T)() +if (isCpu!T) +{ + string[] tmp1 = new string[256], tmp2 = new string[256]; + tmp2[] = " setups2 = &setup_data_none!T;\n"; + + void call_addr(const(ubyte[]) list, string fname) + { + foreach(op; list) { - (*cycles) += cyc; - wrappedTick(cyc); + tmp1[op] = + " setups1 = &setup_address_" ~ fname ~ "!T;\n"; } } + + void call_data(const(ubyte[]) list, string fname) + { + foreach(op; list) + { + tmp2[op] = + " setups2 = &setup_data_" ~ fname ~ "!T;\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"); + call_addr([0x00], "op_BRK"); + call_addr([0x20], "op_JSR"); + call_addr([0x40, 0x60], "op_RTx"); + call_addr([0x4C], "op_JMP_abs"); + call_addr([0x6C], "op_JMP_ind"); + + call_data([0x08], "op_PHP"); + call_data([0x28], "op_PLP"); + call_data([0x00], "op_BRK"); + call_data([0x40], "op_RTI"); + static if (isNMOS!T) + { + call_addr(HLT_OPS!T, "none"); + + call_data([0x48], "push"); + call_data([0x68], "pull"); + } else { - void tick() + call_addr(ZPI_OPS!T, "zpi"); + call_addr(NOP1_OPS!T, "reg"); + call_addr([0x5C], "op_5C"); + call_addr([0x7C], "op_JMP_inx"); + + call_data([0x48, 0x5A, 0xDA], "push"); + call_data([0x68, 0x7A, 0xFA], "pull"); + } + + 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"; + } + return ret ~ "\n}"; +} + + +template addrsetup_t(T) +{ + alias Block[] delegate(T, out ushort, out string) addrsetup_t; +} + +template datasetup_t(T) +{ + alias Block[] delegate(T, ushort, out string) datasetup_t; +} + + +auto setup_address_none(T)(ubyte opcode) +if (isCpu!T) +{ + return cast(addrsetup_t!T[])[]; +} + +auto setup_address_imm(T)(ubyte opcode) +if (isCpu!T) +{ + assert(IMM_OPS!T.canFind(opcode)); + + auto setup(T cpu, out ushort addr, out string name) + { + name = "imm"; + addr = 0x1001; + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode])]; + } + + return [&setup]; +} + +auto setup_address_zpg(T)(ubyte opcode) +if (isCpu!T) +{ + assert(ZPG_OPS!T.canFind(opcode)); + + auto setup(T cpu, out ushort addr, out string name) + { + name = "zpg"; + addr = 0x0070; + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode, 0x70])]; + } + + return [&setup]; +} + +auto setup_address_zpxy(T)(ubyte opcode) +if (isCpu!T) +{ + 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) + { + name = (useX ? "zpx" : "zpy"); + addr = pageWrapAdd(0x0070, idx); + if (useX) { - (*cycles)++; - wrappedTick(); + setX(cpu, idx); setY(cpu, 0x10); + } + else + { + setY(cpu, idx); setX(cpu, 0x10); + } + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode, 0x70])]; + } + + 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) +{ + assert(ABS_OPS!T.canFind(opcode)); + + auto setup(T cpu, out ushort addr, out string name) + { + name = "abs"; + addr = 0x10C5; + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode, 0xC5, 0x10])]; + } + + return [&setup]; +} + +auto setup_address_zpi(T)(ubyte opcode) +if (isCpu!T && isCMOS!T) +{ + assert(ZPI_OPS!T.canFind(opcode)); + + auto setup_nowrap(T cpu, out ushort addr, out string name) + { + 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]; +} + +auto setup_address_izx(T)(ubyte opcode) +if (isCpu!T) +{ + assert(IZX_OPS!T.canFind(opcode)); + + auto setup_nowrap(T cpu, out ushort addr, out string name) + { + name = "izx no-wrap"; + addr = 0x10C5; + setPC(cpu, 0x1000); + setX(cpu, 0x20); + return [Block(0x1000, [opcode, 0x70]), + Block(0x0090, [0xC5, 0x10])]; + } + + 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]; +} + +auto setup_address_izy(T)(ubyte opcode) +if (isCpu!T) +{ + assert(IZY_OPS!T.canFind(opcode)); + + auto setup_nowrap(T cpu, out ushort addr, out string name) + { + name = "izy no-wrap"; + addr = pageCrossAdd(0x10C5, 0x20); + setY(cpu, 0x20); + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode, 0x70]), + Block(0x0070, [0xC5, 0x10])]; + } + + 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])]; + } + + 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]; +} + +auto setup_address_abxy(T)(ubyte opcode) +if (isCpu!T) +{ + 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) + { + 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])]; + + } + + 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]; +} + +auto setup_address_reg(T)(ubyte opcode) +if (isCpu!T) +{ + 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]; +} + +auto setup_address_push(T)(ubyte opcode) +if (isCpu!T) +{ + assert(PUSH_OPS!T.canFind(opcode)); + + auto setup_nowrap(T cpu, out ushort addr, out string name) + { + name = "non-wrapping"; + setSP(cpu, 0xFE); + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode])]; + } + + auto setup_wrap(T cpu, out ushort addr, out string name) + { + name = "wrapping"; + setSP(cpu, 0x00); + setPC(cpu, 0x1000); + return [Block(0x1000, [opcode])]; + } + + return [&setup_nowrap, &setup_wrap]; +} + +auto setup_address_pull(T)(ubyte opcode) +if (isCpu!T) +{ + assert(PULL_OPS!T.canFind(opcode)); + + auto setup_nowrap(T cpu, out ushort addr, out string name) + { + name = "non-wrapping"; + setSP(cpu, 0x01); + setPC(cpu, 0x1000); + return [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]; +} + +auto setup_address_branch(T)(ubyte opcode) +if (isCpu!T) +{ + 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) + { + 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(0x1000, []), // for wrong-page read + Block(getPC(cpu), [opcode, values[count++]])]; + } + else + { + setPC(cpu, 0x1000); + return [Block(getPC(cpu), [opcode, values[count++]])]; } } - cpu.tick = &tick; - return constRef(cycles); + return [&setup, &setup, &setup, &setup, &setup]; } - -enum Action : ushort { NONE, READ, WRITE } - -struct Bus -{ - Action action; - ushort addr; - - string toString() const - { - return format("Bus(%s, %0.4X)", to!string(action), addr); - } -} - -/* - * - */ -const(Bus[]) recordBus(T)(T cpu, int actions = 8) +auto setup_address_op_BRK(T)(ubyte opcode) if (isCpu!T) { - auto record = new Bus[actions]; - int c; + assert(opcode == 0x00); - enforce(cpu.memoryRead !is null && cpu.memoryWrite !is null); - auto wrappedRead = cpu.memoryRead; - auto wrappedWrite = cpu.memoryWrite; - - ubyte read(ushort addr) + auto setup(T cpu, out ushort addr) { - if (c == actions) - throw new TestException( - format("cannot record more than %d actions", actions)); - record[c++] = Bus(Action.READ, addr); - return wrappedRead(addr); + addr = 0xFE55; + setPC(cpu, 0xFD00); + 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])]; } - void write(ushort addr, ubyte val) + auto setup_nowrap(T cpu, out ushort addr, out string name) { - if (c == actions) - throw new TestException( - format("cannot record more than %d actions", actions)); - record[c++] = Bus(Action.WRITE, addr); - wrappedWrite(addr, val); + name = "BRK no-wrap"; + setSP(cpu, 0xFF); + return setup(cpu, addr); } - cpu.memoryRead = &read; - cpu.memoryWrite = &write; + auto setup_wrap(T cpu, out ushort addr, out string name) + { + name = "BRK wrap"; + setSP(cpu, 0x01); + return setup(cpu, addr); + } - return record; + return [&setup_nowrap, &setup_wrap]; } - -class StopException : Exception { this(string msg) { super(msg); } } - -void runUntilBRK(T)(T cpu) +auto setup_address_op_JSR(T)(ubyte opcode) if (isCpu!T) { - enforce(cpu.memoryRead !is null); - auto wrappedRead = cpu.memoryRead; + assert(opcode == 0x20); - ubyte read(ushort addr) + auto setup(T cpu, out ushort addr) { - if (addr == 0xFFFE) throw new StopException("BRK"); - return wrappedRead(addr); + addr = 0x10C5; + setPC(cpu, 0x1000); + auto sp = getSP(cpu); + auto sp1 = pageWrapAdd(sp, 1); + return [Block(0x1000, [0x20, 0xC5, 0x10]), + Block(sp, [notXX(0x10)]), + Block(sp1, [notXX(0x02)])]; + } - cpu.memoryRead = &read; + auto setup_nowrap(T cpu, out ushort addr, out string name) + { + name = "JSR no-wrap"; + setSP(cpu, 0xFF); + return setup(cpu, addr); + } - try { cpu.run(true); } catch (StopException e) {} + 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]; } -void runOneOpcode(T)(T cpu) +auto setup_address_op_RTx(T)(ubyte opcode) if (isCpu!T) { - cpu.run(false); + assert(opcode == 0x40 || opcode == 0x60); + + auto setup(T cpu, out ushort addr, out string name, ubyte opcode) + { + 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"; + setPC(cpu, 0xFD00); + return [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]; +} + + + +// XXX data not anything put in memory by setup_address_* ? + +auto setup_data_none(T)(ubyte opcode) +if (isCpu!T) +{ + auto setup(T cpu, ushort addr, out string name) + { + name = ""; + return cast(Block[])[]; + } + + return [&setup]; +} + +auto setup_data_push(T)(ubyte opcode) +if (isCpu!T) +{ + static if (isNMOS!T) + assert(opcode == 0x48); + 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++]])]; + } + + return [&setup, &setup, &setup]; +} + +auto setup_data_op_BRK(T)(ubyte opcode) +if (isCpu!T) +{ + assert(opcode == 0x00); + // XXX cpu flags each set/unset, with corresponding garbage values + // at sp+2 + return cast(datasetup_t!T[])[]; +} + +auto setup_data_op_PHP(T)(ubyte opcode) +if (isCpu!T) +{ + assert(opcode == 0x08); + // XXX cpu flags each set/unset, with corresponding garbage values + return cast(datasetup_t!T[])[]; +} + +auto setup_data_op_RTI(T)(ubyte opcode) +if (isCpu!T) +{ + assert(opcode == 0x40); + // XXX values for each flag set/unset, with flags corresponding + return cast(datasetup_t!T[])[]; +} + +auto setup_data_op_PLP(T)(ubyte opcode) +if (isCpu!T) +{ + assert(opcode == 0x28); + // XXX values for each flag set/unset, with flags corresponding + return cast(datasetup_t!T[])[]; +} + +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!(Cmos!(false, false))(); +// writeln(foo); } diff --git a/test/cmdfile b/test/cmdfile index a94d15a..eded843 100644 --- a/test/cmdfile +++ b/test/cmdfile @@ -1,2 +1,2 @@ --unittest -I.. -I../src -inline -release -O +-unittest -I.. -I../src -g diff --git a/test/test_decimal.d b/test/test_decimal.d index 8c6a134..aa78dc3 100644 --- a/test/test_decimal.d +++ b/test/test_decimal.d @@ -213,10 +213,13 @@ if (isCpu!T) } auto cpu = makeCpu!T(mem); - cpu.programCounter = 0x8000; + setPC(cpu, 0x8000); runUntilBRK(cpu); if (mem[0x8003]) + { + // TODO: check data block to find out what failed exactly throw new TestException("failed decimal mode " ~ T.stringof); + } } diff --git a/test/wrap6502.d b/test/wrap6502.d new file mode 100644 index 0000000..74c8d14 --- /dev/null +++ b/test/wrap6502.d @@ -0,0 +1,158 @@ +module test.wrap6502; + + +public import d6502.nmosundoc : NmosUndoc; +public import d6502.cmos : Cmos; + +import test.base; + + +// True if T is the type of a cpu. +template isCpu(T) +{ + enum isCpu = __traits(hasMember, T, "_isCpuBase"); +} + +// True if the cpu type T represents a 6502. +template isNMOS(T) +{ + enum isNMOS = __traits(hasMember, T, "_isNMOS"); +} + +// True if the cpu type T represents a 65C02. +template isCMOS(T) +{ + enum isCMOS = __traits(hasMember, T, "_isCMOS"); +} + +// True if the cpu type T accesses memory on every cycle. +template isStrict(T) +{ + enum isStrict = __traits(hasMember, T, "_isStrict"); +} + +// True if the cpu type T +template isCumulative(T) +{ + enum isCumulative = __traits(hasMember, T, "_isCumulative"); +} + + +template CPU(string type, bool strict, bool cumulative) +{ + static if (type == "65c02" || type == "65C02") + alias Cmos!(strict, cumulative) CPU; + else static if (type == "6502") + alias NmosUndoc!(strict, cumulative) CPU; + else static assert(0); +} + + +/* + * Connects a cpu and memory. + */ +void connectCpu(T)(T cpu, ref TestMemory mem) +if (isCpu!T) +{ + static if (isCumulative!T) + void tick(int cycles) {} + else + void tick() {} + + cpu.memoryRead = &mem.read; + cpu.memoryWrite = &mem.write; + cpu.tick = &tick; +} + + +class StopException : Exception { this(string msg) { super(msg); } } + +void runUntilBRK(T)(T cpu) +if (isCpu!T) +{ + assert(cpu.memoryRead !is null); + auto wrappedRead = cpu.memoryRead; + + ubyte read(ushort addr) + { + if (addr == 0xFFFE) throw new StopException("BRK"); + return wrappedRead(addr); + } + + cpu.memoryRead = &read; + + try { cpu.run(true); } catch (StopException e) {} +} + + +void runOneOpcode(T)(T cpu) +if (isCpu!T) +{ + cpu.run(false); +} + +void setPC(T)(T cpu, int addr) +if (isCpu!T) +{ + cpu.programCounter = cast(ushort)addr; +} + +ushort getPC(T)(T cpu) +if (isCpu!T) +{ + return cpu.programCounter; +} + +void setSP(T)(T cpu, int val) +if (isCpu!T) +{ + cpu.stackPointer = cast(ubyte)val; +} + +ushort getSP(T)(T cpu) +if (isCpu!T) +{ + return 0x100 | cpu.stackPointer; +} + +void setX(T)(T cpu, int val) +if (isCpu!T) +{ + cpu.xIndex = cast(ubyte)val; +} + +ubyte getX(T)(T cpu) +if (isCpu!T) +{ + return cpu.xIndex; +} + +void setY(T)(T cpu, int val) +if (isCpu!T) +{ + cpu.yIndex = cast(ubyte)val; +} + +ubyte getY(T)(T cpu) +if (isCpu!T) +{ + return cpu.yIndex; +} + +void setFlag(T)(T cpu, Flag f) +if (isCpu!T) +{ + cpu.flag.fromByte(cpu.flag.toByte() | f); +} + +void clearFlag(T)(T cpu, Flag f) +if (isCpu!T) +{ + cpu.flag.fromByte(cpu.flag.toByte() & ~f); +} + +bool getFlag(T)(T cpu, Flag f) +if (isCpu!T) +{ + return (cpu.flag.toByte() & f) != 0; +}