Test framework reorginization
This commit is contained in:
parent
712b7547c6
commit
1b9312ac97
639
test/base.d
639
test/base.d
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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); } }
|
|
@ -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";
|
||||
}
|
110
test/test_bus.d
110
test/test_bus.d
|
@ -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);
|
||||
|
|
|
@ -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])
|
||||
|
|
158
test/wrap6502.d
158
test/wrap6502.d
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue