mirror of
https://github.com/edmccard/twoapple-reboot.git
synced 2025-01-02 23:30:42 +00:00
1235 lines
30 KiB
D
1235 lines
30 KiB
D
module test.base;
|
|
|
|
|
|
import std.algorithm, std.conv, std.exception, std.random, std.range,
|
|
std.string, std.traits;
|
|
|
|
public import test.wrap6502;
|
|
|
|
|
|
class TestException : Exception { this(string msg) { super(msg); } }
|
|
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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 ...)
|
|
{
|
|
foreach (block; blocks)
|
|
{
|
|
auto base = block.base;
|
|
auto data = block.data;
|
|
if (base < 0x200)
|
|
{
|
|
enforce(base + data.length <= 0x200,
|
|
format("Address out of bounds %0.4x", base));
|
|
data1[base..base + data.length] = data[];
|
|
}
|
|
else
|
|
{
|
|
if (!data2_base)
|
|
{
|
|
if (base > 0xFD00)
|
|
data2_base = 0xFD00;
|
|
else
|
|
data2_base = base;
|
|
data2_max = base + 0x300;
|
|
}
|
|
enforce(base + data.length <= data2_max,
|
|
format("Address out of bounds %0.4x", base));
|
|
auto last = base + data.length;
|
|
data2[base-data2_base..last-data2_base] = data[];
|
|
}
|
|
}
|
|
enforce(data2_base, "Missing memory > 0x0200");
|
|
}
|
|
|
|
ubyte read(ushort addr)
|
|
{
|
|
if (addr < 0x0200)
|
|
return data1[addr];
|
|
else if (addr >= data2_base && addr < data2_max)
|
|
return data2[addr - data2_base];
|
|
else
|
|
throw new TestException(format("read %0.4x", addr));
|
|
}
|
|
|
|
void write(ushort addr, ubyte val)
|
|
{
|
|
if (addr < 0x0200)
|
|
data1[addr] = val;
|
|
else if (addr >= data2_base && addr < data2_max)
|
|
data2[addr - data2_base] = val;
|
|
else
|
|
throw new TestException(format("write %0.4x", addr));
|
|
}
|
|
|
|
ubyte opIndex(size_t i1)
|
|
{
|
|
auto addr = cast(ushort)i1;
|
|
enforce(addr < 0x0200 || (addr >= data2_base && addr < data2_max),
|
|
"Read out of bounds");
|
|
return read(addr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A block of memory with a given base address.
|
|
*/
|
|
struct Block
|
|
{
|
|
ushort base;
|
|
ubyte[] data;
|
|
|
|
string toString() const
|
|
{
|
|
return format("Block(%0.4X, %s)", base, formatMemory());
|
|
}
|
|
|
|
string formatMemory(int max = 3) const
|
|
{
|
|
if (max > data.length) max = data.length;
|
|
auto hexbytes = map!(`format("%0.2X", a)`)(data[0..max]);
|
|
auto ret = join(array(hexbytes), " ");
|
|
if (data.length > max)
|
|
ret ~= format(" (%d more bytes)", data.length - max);
|
|
return "[" ~ ret ~ "]";
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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++]])];
|
|
}
|
|
}
|
|
|
|
return [&setup, &setup, &setup, &setup, &setup];
|
|
}
|
|
|
|
auto setup_address_op_BRK(T)(ubyte opcode)
|
|
if (isCpu!T)
|
|
{
|
|
assert(opcode == 0x00);
|
|
|
|
auto setup(T cpu, out ushort 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])];
|
|
}
|
|
|
|
auto setup_nowrap(T cpu, out ushort addr, out string name)
|
|
{
|
|
name = "BRK no-wrap";
|
|
setSP(cpu, 0xFF);
|
|
return setup(cpu, addr);
|
|
}
|
|
|
|
auto setup_wrap(T cpu, out ushort addr, out string name)
|
|
{
|
|
name = "BRK wrap";
|
|
setSP(cpu, 0x01);
|
|
return setup(cpu, addr);
|
|
}
|
|
|
|
return [&setup_nowrap, &setup_wrap];
|
|
}
|
|
|
|
auto setup_address_op_JSR(T)(ubyte opcode)
|
|
if (isCpu!T)
|
|
{
|
|
assert(opcode == 0x20);
|
|
|
|
auto setup(T cpu, out ushort 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)])];
|
|
|
|
}
|
|
|
|
auto setup_nowrap(T cpu, out ushort addr, out string name)
|
|
{
|
|
name = "JSR no-wrap";
|
|
setSP(cpu, 0xFF);
|
|
return setup(cpu, addr);
|
|
}
|
|
|
|
auto setup_wrap(T cpu, out ushort addr, out string name)
|
|
{
|
|
name = "JSR wrap";
|
|
setSP(cpu, 0x00);
|
|
return setup(cpu, addr);
|
|
}
|
|
|
|
return [&setup_nowrap, &setup_wrap];
|
|
}
|
|
|
|
|
|
auto setup_address_op_RTx(T)(ubyte opcode)
|
|
if (isCpu!T)
|
|
{
|
|
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);
|
|
}
|