Test framework reorginization

This commit is contained in:
edmccard 2012-03-31 15:44:23 -04:00
parent 712b7547c6
commit 1b9312ac97
8 changed files with 940 additions and 741 deletions

View File

@ -1,13 +1,9 @@
module test.base;
import std.algorithm, std.conv, std.exception, std.random, std.range,
std.string, std.traits;
import std.algorithm, std.conv, std.exception, std.range, std.string;
public import test.wrap6502;
class TestException : Exception { this(string msg) { super(msg); } }
import test.cpu, test.opcodes;
/*
@ -29,10 +25,10 @@ public:
* 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).
* base address, but note that the base of the 3-page "main
* memory" will be the start of the page that contains the first
* block with a base address greater than 0x01FF (there must be at
* least one such block).
*/
this(const Block[] blocks ...)
{
@ -53,8 +49,8 @@ public:
if (base > 0xFD00)
data2_base = 0xFD00;
else
data2_base = base;
data2_max = base + 0x300;
data2_base = base & 0xFF00;
data2_max = data2_base + 0x300;
}
enforce(base + data.length <= data2_max,
format("Address out of bounds %0.4x", base));
@ -119,477 +115,6 @@ struct Block
}
struct Ref(T)
if (isPointer!T)
{
private const(T) data;
this(T ptr) { data = ptr; }
auto deref() { return *data; }
alias deref this;
string toString () const { return format("%s", *data); }
}
auto constRef(T)(T ptr)
if (isPointer!T)
{
return Ref!(const(T))(ptr);
}
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)
{
if (val)
setFlag(cpu, f);
else
clearFlag(cpu, f);
}
void expectBranch(T)(T cpu, ubyte opcode)
if (isCpu!T)
{
switch (opcode)
{
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)
{
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
{
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;
@ -922,8 +447,7 @@ if (isCpu!T)
if (name == "forward-px")
{
setPC(cpu, 0x1081);
return [Block(0x1000, []), // for wrong-page read
Block(getPC(cpu), [opcode, values[count++]])];
return [Block(getPC(cpu), [opcode, values[count++]])];
}
else
{
@ -1203,6 +727,149 @@ if (isCpu!T)
return cast(datasetup_t!T[])[];
}
auto setup_data_dec_reg(T)(ubyte opcode)
if (isCpu!T)
{
/* XXX DEX, DEY, on CMOS DEC A
* set reg to:
* 0x01 (sets z) 0x00 (sets N) 0x80 (clears both)
*/
}
auto setup_data_dec(T)(ubyte opcode)
if (isCpu!T)
{
// XXX all addressing modes of DEC
// set addr to
// 0x01 (sets z) 0x00 (sets N) 0x80 (clears both)
}
auto setup_data_inc_reg(T)(ubyte opcode)
if (isCpu!T)
{
// XXX INX, INY, on CMOS INC A
// set reg to:
// 0xFF (sets Z) 0x7F (sets N) 0x00 (clears both)
}
auto setup_data_inc(T)(ubyte opcode)
if (isCpu!T)
{
// XXX all addressing modes of INC
// set addr to:
// 0xFF (sets Z) 0x7F (sets N) 0x00 (clears both)
}
auto setup_data_rol(T)(ubyte opcode)
if (isCpu!T)
{
// 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)
}
auto setup_data_asl(T)(ubyte opcode)
if (isCpu!T)
{
// 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
}
// 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)
{
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"); // 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
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");
static if (isNMOS!T)
{
call_addr(HLT_OPS!T, "none");
call_data([0x48], "push");
call_data([0x68], "pull");
}
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");
}
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}";
}
unittest
{
/+
@ -1229,6 +896,6 @@ unittest
}
}
+/
// enum foo = getMemSetup!(Cmos!(false, false))();
// enum foo = getMemSetup!(NmosUndoc!(false, false))();
// writeln(foo);
}

442
test/cpu.d Normal file
View File

@ -0,0 +1,442 @@
/*
* Test functionality that either depends on a specific cpu
* implementation, or that may be useful to any test which requires a
* cpu (as opposed to testing the cpu itself).
*/
module test.cpu;
import std.conv, std.exception, std.random, std.string, std.traits;
public import d6502.nmosundoc : NmosUndoc;
public import d6502.cmos : Cmos;
// 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 aggregates ticks.
template isCumulative(T)
{
enum isCumulative = __traits(hasMember, T, "_isCumulative");
}
/*
* The type of a cpu, based on its architecture (6502 or 65C02) and
* its timing characteristics (strict or not bus access, cumulative or
* not cycle reporting).
*/
template CPU(string arch, bool strict, bool cumulative)
{
static if (arch == "65c02" || arch == "65C02")
alias Cmos!(strict, cumulative) CPU;
else static if (arch == "6502")
alias NmosUndoc!(strict, cumulative) CPU;
else static assert(0);
}
// Connects test memory to a cpu.
void connectMem(T, S)(T cpu, ref S 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;
}
/*
* Sets up a cpu to record the number of cycles executed.
*
* For example:
*
* auto cycles = recordCycles(cpu);
* // run a test
* if (cycles != expected) ...
*/
auto recordCycles(T)(T cpu)
if (isCpu!T)
{
auto cycles = new int;
auto wrappedTick = cpu.tick;
static if (isCumulative!T)
{
void tick(int cyc)
{
(*cycles) += cyc;
wrappedTick(cyc);
}
}
else
{
void tick()
{
(*cycles)++;
wrappedTick();
}
}
cpu.tick = &tick;
return constRef(cycles);
}
struct Ref(T)
if (isPointer!T)
{
private const(T) data;
this(T ptr) { data = ptr; }
auto deref() { return *data; }
alias deref this;
string toString () const { return format("%s", *data); }
}
auto constRef(T)(T ptr)
if (isPointer!T)
{
return Ref!(const(T))(ptr);
}
/*
* Sets up a cpu to record bus accesses during execution.
*
* For example:
*
* auto accesses = recordBus(cpu);
* // run a test
* if (accesses != expected) ...
*/
const(Bus[]) recordBus(T)(T cpu, int actions = 8)
if (isCpu!T)
{
auto record = new Bus[actions];
int c;
enforce(cpu.memoryRead !is null && cpu.memoryWrite !is null);
auto wrappedRead = cpu.memoryRead;
auto wrappedWrite = cpu.memoryWrite;
ubyte read(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);
}
void write(ushort addr, ubyte val)
{
if (c == actions)
throw new TestException(
format("cannot record more than %d actions", actions));
record[c++] = Bus(Action.WRITE, addr);
wrappedWrite(addr, val);
}
cpu.memoryRead = &read;
cpu.memoryWrite = &write;
return record;
}
// A record of a bus access with its type and address.
struct Bus
{
Action action;
ushort addr;
this(Action action, int addr)
{
this.action = action; this.addr = cast(ushort)addr;
}
string toString() const
{
return format("Bus(%s, %0.4X)", to!string(action), addr);
}
}
// Types of bus accesses.
enum Action : ushort { NONE, READ, WRITE }
// Runs the cpu until a BRK instruction.
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) {}
}
class StopException : Exception { this(string msg) { super(msg); } }
// Runs the cpu for one opcode.
void runOneOpcode(T)(T cpu)
if (isCpu!T)
{
cpu.run(false);
}
// Sets the program counter.
void setPC(T)(T cpu, int addr)
if (isCpu!T)
{
cpu.programCounter = cast(ushort)addr;
}
// Returns the program counter.
ushort getPC(T)(T cpu)
if (isCpu!T)
{
return cpu.programCounter;
}
/*
* Sets the stack pointer.
*
* Can be called with either an offset (e.g., 0xFE) or an absolute
* stack address (e.g., 0x01FE).
*/
void setSP(T)(T cpu, int val)
if (isCpu!T)
{
assert(val < 0x0200);
cpu.stackPointer = cast(ubyte)val;
}
/*
* Returns the stack address (in the range 0x0100-0x01FF) represented
* by the stack pointer.
*/
ushort getSP(T)(T cpu)
if (isCpu!T)
{
return 0x100 | cpu.stackPointer;
}
// Sets the X register.
void setX(T)(T cpu, int val)
if (isCpu!T)
{
cpu.xIndex = cast(ubyte)val;
}
// Returns the X register.
ubyte getX(T)(T cpu)
if (isCpu!T)
{
return cpu.xIndex;
}
// Sets the Y register.
void setY(T)(T cpu, int val)
if (isCpu!T)
{
cpu.yIndex = cast(ubyte)val;
}
// Returns the Y register.
ubyte getY(T)(T cpu)
if (isCpu!T)
{
return cpu.yIndex;
}
// The names of the status flags.
enum Flag : ubyte
{
C = 0x01,
Z = 0x02,
I = 0x04,
D = 0x08,
V = 0x40,
N = 0x80
}
// Sets one or more status flags.
void setFlag(T)(T cpu, Flag[] flags...)
if (isCpu!T)
{
auto reg = cpu.flag.toByte();
foreach (flag; flags) reg |= flag;
cpu.flag.fromByte(reg);
}
// Clears one or more status flags.
void clearFlag(T)(T cpu, Flag[] flags...)
if (isCpu!T)
{
auto reg = cpu.flag.toByte();
foreach (flag; flags) reg &= ~flag;
cpu.flag.fromByte(reg);
}
// Returns a status flag.
bool getFlag(T)(T cpu, Flag f)
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)
if (isCpu!T)
{
if (val)
setFlag(cpu, f);
else
clearFlag(cpu, f);
}
// Sets or clears the flag required for a given opcode to branch.
void expectBranch(T)(T cpu, ubyte opcode)
if (isCpu!T)
{
switch (opcode)
{
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));
}
}
// Returns whether an opcode would branch if executed.
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));
}
}
// Sets or clears the flag required for a given opcode to not branch.
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));
}
}
// Constructs an address from its low and high bytes.
ushort address(ubyte l, ubyte h)
{
return cast(ushort)((h << 8) | l);
}
/*
* Adds an offset to an address, resulting in an address in the same
* page.
*/
ushort pageWrapAdd(ushort base, int offset)
{
return (base & 0xFF00) + cast(ubyte)((base & 0xFF) + offset);
}
/*
* Adds an offset to an address, possibly resulting in an address in a
* different page.
*/
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);
}
class TestException : Exception { this(string msg) { super(msg); } }

326
test/opcodes.d Normal file
View File

@ -0,0 +1,326 @@
module test.opcodes;
import test.cpu;
// 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";
}
// Opcodes which decrement a register.
template OPS_DEC_REG(T)
if (isCpu!T)
{
static if (isNMOS!T)
enum OPS_DEC_REG = cast(ubyte[])x"88 CA";
else
enum OPS_DEC_REG = cast(ubyte[])x"3A 88 CA";
}
// Opcodes which increment a register.
template OPS_INC_REG(T)
if (isCpu!T)
{
static if (isNMOS!T)
enum OPS_INC_REG = cast(ubyte[])x"C8 E8";
else
enum OPS_INC_REG = cast(ubyte[])x"1A C8 E8";
}
// Opcodes which decrement a memory location.
template OPS_DEC(T)
if (isCpu!T)
{
enum OPS_DEC = cast(ubyte[])x"C6 CE D6 DE";
}
// Opcodes which increment a memory location.
template OPS_INC(T)
if (isCpu!T)
{
enum OPS_INC = cast(ubyte[])x"E6 EE F6 FE";
}
// Opcodes which rotate a value left.
template OPS_ROL(T)
if (isCpu!T)
{
enum OPS_ROL = cast(ubyte[])x"26 2A 2E 36 3E";
}
// Opcodes which shift a value left.
template OPS_ASL(T)
if (isCpu!T)
{
enum OPS_ASL = cast(ubyte[])x"06 0A 0E 16 1E";
}

View File

@ -1,7 +1,10 @@
module test_bus;
module test.test_bus;
import std.algorithm, std.array, std.conv, std.exception, std.string;
import test.base;
import std.algorithm, std.array, std.conv, std.exception, std.stdio,
std.string;
import test.base, test.cpu, test.opcodes;
T[] If(alias cond, T)(T[] actions)
@ -13,11 +16,6 @@ T[] If(alias cond, T)(T[] actions)
}
template timesetup_t(T)
{
alias Bus[] function(T, ref TestMemory, out int) timesetup_t;
}
/// Bus access pattern for register opcodes.
auto accesses_reg(T)(T cpu, ref TestMemory mem, out int cycles)
if (isCpu!T)
@ -561,89 +559,7 @@ if (isCpu!T && isCMOS!T)
}
enum Action : ushort { NONE, READ, WRITE }
struct Bus
{
Action action;
ushort addr;
this(Action action, int addr)
{
this.action = action; this.addr = cast(ushort)addr;
}
string toString() const
{
return format("Bus(%s, %0.4X)", to!string(action), addr);
}
}
/*
*
*/
const(Bus[]) recordBus(T)(T cpu, int actions = 8)
if (isCpu!T)
{
auto record = new Bus[actions];
int c;
enforce(cpu.memoryRead !is null && cpu.memoryWrite !is null);
auto wrappedRead = cpu.memoryRead;
auto wrappedWrite = cpu.memoryWrite;
ubyte read(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);
}
void write(ushort addr, ubyte val)
{
if (c == actions)
throw new TestException(
format("cannot record more than %d actions", actions));
record[c++] = Bus(Action.WRITE, addr);
wrappedWrite(addr, val);
}
cpu.memoryRead = &read;
cpu.memoryWrite = &write;
return record;
}
auto recordCycles(T)(T cpu)
if (isCpu!T)
{
auto cycles = new int;
auto wrappedTick = cpu.tick;
static if (isCumulative!T)
{
void tick(int cyc)
{
(*cycles) += cyc;
wrappedTick(cyc);
}
}
else
{
void tick()
{
(*cycles)++;
wrappedTick();
}
}
cpu.tick = &tick;
return constRef(cycles);
}
// Associates opcodes with expected access patterns.
string getExpected(T)()
{
string[] tmp = new string[256];
@ -689,8 +605,14 @@ string getExpected(T)()
return "final switch (opcode)\n{\n" ~ join(tmp, "\n") ~ "\n}";
}
import std.stdio;
template timesetup_t(T)
{
alias Bus[] function(T, ref TestMemory, out int) timesetup_t;
}
// Tests the bus access patterns and cycles taken for a given opcode.
void test_opcode_timing(T)(ubyte opcode)
{
addrsetup_t!T[] function(ubyte) setups1;
@ -709,7 +631,7 @@ void test_opcode_timing(T)(ubyte opcode)
auto cpu = new T();
auto block1 = func1(cpu, addr, name1);
auto mem = TestMemory(block1);
connectCpu(cpu, mem);
connectMem(cpu, mem);
auto exp = expected(cpu, mem, cycles);
exp = exp ~ new Bus[8 - exp.length];
auto actual = recordBus(cpu);
@ -748,8 +670,6 @@ void test_opcode_timing(T)(ubyte opcode)
unittest
{
import std.stdio;
alias CPU!("65C02", false, false) T1;
for (int op = 0x00; op < 0x100; op++)
test_opcode_timing!T1(cast(ubyte)op);

View File

@ -1,7 +1,9 @@
module test.test_decimal;
import std.stdio;
import test.base;
import test.base, test.cpu;
void testDecimalMode(T)()
@ -213,7 +215,7 @@ if (isCpu!T)
}
auto cpu = new T();
connectCpu(cpu, mem);
connectMem(cpu, mem);
setPC(cpu, 0x8000);
runUntilBRK(cpu);
if (mem[0x8003])

View File

@ -1,158 +0,0 @@
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;
}