2012-03-23 16:14:15 +00:00
|
|
|
module test.base;
|
|
|
|
|
|
|
|
|
2012-04-12 22:42:55 +00:00
|
|
|
import std.algorithm, std.array, std.conv, std.exception, std.getopt,
|
|
|
|
std.stdio, std.string;
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-03-31 19:44:23 +00:00
|
|
|
import test.cpu, test.opcodes;
|
2012-04-12 22:42:55 +00:00
|
|
|
import cpu.data_d6502;
|
2012-03-23 16:14:15 +00:00
|
|
|
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
2012-04-03 00:58:51 +00:00
|
|
|
*
|
|
|
|
* The contents are initialized to 0xFF. Individual locations can be
|
|
|
|
* read and written using array index syntax.
|
2012-03-23 16:14:15 +00:00
|
|
|
*/
|
|
|
|
struct TestMemory
|
|
|
|
{
|
2012-03-25 08:25:41 +00:00
|
|
|
private:
|
2012-04-03 00:58:51 +00:00
|
|
|
ubyte[0x200] data1 = 0xFF;
|
|
|
|
ubyte[0x300] data2 = 0xFF;
|
|
|
|
ushort data2_base;
|
|
|
|
size_t data2_max;
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
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
|
2012-03-31 19:44:23 +00:00
|
|
|
* 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).
|
2012-03-25 08:25:41 +00:00
|
|
|
*/
|
2012-04-03 00:58:51 +00:00
|
|
|
this(const(Block[]) blocks ...)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
|
|
|
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));
|
2012-03-25 08:25:41 +00:00
|
|
|
data1[base..base + data.length] = data[];
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!data2_base)
|
|
|
|
{
|
2012-03-25 08:25:41 +00:00
|
|
|
if (base > 0xFD00)
|
|
|
|
data2_base = 0xFD00;
|
|
|
|
else
|
2012-03-31 19:44:23 +00:00
|
|
|
data2_base = base & 0xFF00;
|
|
|
|
data2_max = data2_base + 0x300;
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
enforce(base + data.length <= data2_max,
|
|
|
|
format("Address out of bounds %0.4x", base));
|
2012-03-25 08:25:41 +00:00
|
|
|
auto last = base + data.length;
|
|
|
|
data2[base-data2_base..last-data2_base] = data[];
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
enforce(data2_base, "Missing memory > 0x0200");
|
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
ubyte read(ushort addr) const
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
ubyte opIndex(size_t i1) const
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
|
|
|
auto addr = cast(ushort)i1;
|
|
|
|
enforce(addr < 0x0200 || (addr >= data2_base && addr < data2_max),
|
|
|
|
"Read out of bounds");
|
|
|
|
return read(addr);
|
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
ubyte opIndexAssign(ubyte val, size_t i1)
|
|
|
|
{
|
|
|
|
auto addr = cast(ushort)i1;
|
|
|
|
enforce(addr < 0x0200 || (addr >= data2_base && addr < data2_max),
|
|
|
|
"Write out of bounds");
|
|
|
|
write(addr, val);
|
|
|
|
return val;
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// A block of memory with a given base address.
|
2012-03-25 08:25:41 +00:00
|
|
|
struct Block
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
const ushort base;
|
|
|
|
const(ubyte[]) data;
|
2012-03-25 08:25:41 +00:00
|
|
|
|
|
|
|
string toString() const
|
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
return format("Block(%0.4X, [%s])", base, formatMemory(data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Formats data as a string of 2-digit hex bytes, separated by spaces.
|
|
|
|
*
|
|
|
|
* If data is longer than max, the string will end with an indication
|
|
|
|
* of the number of extra bytes.
|
|
|
|
*/
|
2012-04-06 22:23:09 +00:00
|
|
|
string formatMemory(const(ubyte[]) data, size_t max = 3)
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
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 OpInfo
|
|
|
|
{
|
|
|
|
ushort addr;
|
|
|
|
ubyte data;
|
|
|
|
int len;
|
|
|
|
bool write;
|
|
|
|
}
|
|
|
|
|
|
|
|
alias void delegate(ubyte, CpuInfo, Block[], OpInfo, string, TestSetup*)
|
|
|
|
testsetup;
|
|
|
|
|
|
|
|
class TestSetup
|
|
|
|
{
|
|
|
|
testsetup setup;
|
|
|
|
TestSetup next;
|
|
|
|
|
|
|
|
auto static opCall(testsetup d)
|
|
|
|
{
|
|
|
|
auto obj = new TestSetup();
|
|
|
|
obj.setup = d;
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
void run(ubyte opcode, CpuInfo cpu = CpuInfo(), Block[] data = [],
|
|
|
|
OpInfo info = OpInfo(), string msg = "")
|
|
|
|
{
|
|
|
|
setup(opcode, cpu, data, info, msg, &next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TestSetup connect(TestSetup first, TestSetup[] rest...)
|
|
|
|
{
|
|
|
|
if (!(rest.empty))
|
|
|
|
{
|
|
|
|
auto x = first;
|
|
|
|
while (x.next !is null) x = x.next;
|
|
|
|
x.next = connect(rest[0], rest[1..$]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return first;
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
template testCallNext()
|
|
|
|
{
|
|
|
|
void callNext(string newMsg = "", Block[] newData = [])
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
if (*next !is null)
|
|
|
|
next.run(opcode, cpu, data ~ newData, info, msg ~ newMsg);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// Does nothing.
|
|
|
|
auto setup_none()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
if (*next !is null) next.run(opcode, cpu, data, info, msg);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// Aborts a test.
|
|
|
|
auto setup_op_abort()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// Prints the current message.
|
|
|
|
auto setup_debug()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
import std.stdio;
|
|
|
|
writeln(format("%s\n data %s\n addr %0.4X op %0.2X %s",
|
|
|
|
msg, data, info.addr, opcode, cpu));
|
|
|
|
if (*next !is null) next.run(opcode, cpu, data, info, msg);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// Splits with the given flag set, then cleared.
|
|
|
|
auto setup_flag(Flag f)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
clearFlag(cpu, f);
|
|
|
|
callNext(format("%s0 ", flagToString(f)));
|
|
|
|
|
|
|
|
setFlag(cpu, f);
|
|
|
|
callNext(format("%s1 ", flagToString(f)));
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// Splits with all flags set, then all flags cleared.
|
|
|
|
auto setup_mask_flags()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
clearFlag(cpu, Flag.C, Flag.Z, Flag.I, Flag.D, Flag.V, Flag.N);
|
|
|
|
callNext("F0 ");
|
|
|
|
|
|
|
|
setFlag(cpu, Flag.C, Flag.Z, Flag.I, Flag.D, Flag.V, Flag.N);
|
|
|
|
callNext("F1 ");
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Used after setup_mask_flags, guarantees coverage of all
|
|
|
|
* combinations of N and Z.
|
|
|
|
*/
|
|
|
|
auto setup_nz()
|
|
|
|
{
|
|
|
|
return setup_flag(Flag.N);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Can be used after setup_mask_flags to cover all combinations of
|
|
|
|
* flags.
|
|
|
|
*/
|
|
|
|
auto setup_nvdzc()
|
|
|
|
{
|
|
|
|
return connect(setup_flag(Flag.N), setup_flag(Flag.Z),
|
|
|
|
setup_flag(Flag.V), setup_flag(Flag.D),
|
|
|
|
setup_flag(Flag.C));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Splits with info.data 0x00, 0x40, and 0x80.
|
|
|
|
auto setup_data()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("zero ");
|
|
|
|
|
|
|
|
info.data = 0x40;
|
|
|
|
callNext("positive ");
|
|
|
|
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("negative ");
|
|
|
|
}
|
|
|
|
return connect(TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* For register transfer opcodes.
|
|
|
|
*
|
|
|
|
* Splits on 0x00, 0x40, and 0x80 in the source register.
|
|
|
|
*/
|
|
|
|
auto setup_op_transfer(Reg source, Reg dest)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
ubyte other = (info.data > 0x00 && info.data < 0x80) ? 0x00 : 0x01;
|
|
|
|
setX(cpu, other); setY(cpu, other); setA(cpu, other);
|
|
|
|
setReg(cpu, source, info.data);
|
|
|
|
callNext("xfer ");
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return connect(setup_nz(), setup_data(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For TXS and TSX.
|
|
|
|
auto setup_op_stack_xfer()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setSP(cpu, 0x00);
|
|
|
|
setX(cpu, 0xFF);
|
|
|
|
callNext("TXS ");
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), TestSetup(&setup));
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* For implied address mode.
|
|
|
|
*
|
|
|
|
* Sets PC to 0x1000 and puts a (1-byte) opcode in memory.
|
|
|
|
*/
|
|
|
|
auto setup_addr_implied()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
info.len = 1;
|
|
|
|
callNext("", [Block(0x1000, [opcode])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* For immediate address mode.
|
|
|
|
*
|
|
|
|
* Sets PC to 0x1000 and info.addr to 0x1001.
|
|
|
|
*/
|
|
|
|
auto setup_addr_imm()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
info.len = 2;
|
|
|
|
info.addr = 0x1001;
|
|
|
|
callNext("imm ", [Block(0x1000, [opcode])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For branch opcodes.
|
|
|
|
*/
|
|
|
|
auto setup_addr_branch(bool cmos)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
if (!cmos || opcode != 0x80)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
expectNoBranch(cpu, opcode);
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
info.addr = 0x1002;
|
|
|
|
callNext("no-branch ", [Block(0x1000, [opcode, 0x10])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
expectBranch(cpu, opcode);
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
setPC(cpu, 0x1000);
|
2012-04-03 00:58:51 +00:00
|
|
|
info.addr = 0x1012;
|
|
|
|
callNext("branch-fwd ", [Block(0x1000, [opcode, 0x10])]);
|
|
|
|
|
|
|
|
setPC(cpu, 0x1081);
|
|
|
|
info.addr = 0x1102;
|
|
|
|
callNext("branch-fwd-px ", [Block(0x1081, [opcode, 0x7F])]);
|
|
|
|
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
info.addr = 0x1000;
|
|
|
|
callNext("branch-bkwd ", [Block(0x1000, [opcode, 0xFE])]);
|
|
|
|
|
|
|
|
setPC(cpu, 0x1100);
|
|
|
|
info.addr = 0x10F7;
|
|
|
|
callNext("branch-bkwd-px ", [Block(0x1000, []),
|
|
|
|
Block(0x1100, [opcode, 0xF5])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* For zeropage adddress mode.
|
|
|
|
*
|
|
|
|
* Sets PC to 0x1000 and puts [opcode, 0x70] in memory.
|
|
|
|
*/
|
|
|
|
auto setup_addr_zpg()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
info.len = 2;
|
|
|
|
info.addr = 0x0070;
|
|
|
|
callNext("zpg ", [Block(0x1000, [opcode, 0x70])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* For absolute address mode.
|
|
|
|
*
|
|
|
|
* Sets PC to 0x1000 and puts [opcode, 0xC5, 0x10] in memory.
|
|
|
|
*/
|
|
|
|
auto setup_addr_abs()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
info.len = 3;
|
|
|
|
info.addr = 0x10C5;
|
|
|
|
callNext("abs ", [Block(0x1000, [opcode, 0xC5, 0x10])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* For zeropage,x/y address modes.
|
|
|
|
*
|
|
|
|
* Sets PC to 0x1000 and puts [opcode, 0x70] in memory.
|
|
|
|
*/
|
|
|
|
auto setup_addr_zpxy(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
string name = (reg == Reg.X ? "zpx " : "zpy ");
|
|
|
|
info.len = 2;
|
2012-03-25 08:25:41 +00:00
|
|
|
setPC(cpu, 0x1000);
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10);
|
|
|
|
setReg(cpu, reg, 0x00);
|
|
|
|
info.addr = pageWrapAdd(0x0070, 0x00);
|
|
|
|
callNext(name ~ "no-idx ", [Block(0x1000, [opcode, 0x70])]);
|
|
|
|
|
|
|
|
setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10);
|
|
|
|
setReg(cpu, reg, 0x20);
|
|
|
|
info.addr = pageWrapAdd(0x0070, 0x20);
|
|
|
|
callNext(name ~ "no-wrap ", [Block(0x1000, [opcode, 0x70])]);
|
|
|
|
|
|
|
|
setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10);
|
|
|
|
setReg(cpu, reg, 0xA0);
|
|
|
|
info.addr = pageWrapAdd(0x0070, 0xA0);
|
|
|
|
callNext(name ~ "wrap ", [Block(0x1000, [opcode, 0x70])]);
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For absolute,x/y address modes.
|
|
|
|
auto setup_addr_abxy(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
string name = (reg == Reg.X ? "abx " : "aby ");
|
|
|
|
info.len = 3;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
|
|
|
|
setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10);
|
|
|
|
setReg(cpu, reg, 0x00);
|
|
|
|
info.addr = pageCrossAdd(0x10C5, 0x00);
|
|
|
|
callNext(name ~ "no-idx ", [Block(0x1000, [opcode, 0xC5, 0x10])]);
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10);
|
|
|
|
setReg(cpu, reg, 0x20);
|
|
|
|
info.addr = pageCrossAdd(0x10C5, 0x20);
|
|
|
|
callNext(name ~ "no-px ", [Block(0x1000, [opcode, 0xC5, 0x10])]);
|
|
|
|
|
|
|
|
setX(cpu, 0x10); setY(cpu, 0x10); setA(cpu, 0x10);
|
|
|
|
setReg(cpu, reg, 0x50);
|
|
|
|
info.addr = pageCrossAdd(0x10C5, 0x50);
|
|
|
|
callNext(name ~ "px ", [Block(0x1000, [opcode, 0xC5, 0x10])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For zeropage indirect,x address mode.
|
|
|
|
auto setup_addr_izx()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.addr = 0x10C5;
|
|
|
|
info.len = 2;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
setA(cpu, 0x01); setY(cpu, 0x01);
|
|
|
|
|
|
|
|
setX(cpu, 0x20);
|
|
|
|
callNext("izx no-wrap ", [Block(0x1000, [opcode, 0x70]),
|
|
|
|
Block(0x0090, [0xC5, 0x10])]);
|
|
|
|
|
|
|
|
setX(cpu, 0xA0);
|
|
|
|
callNext("izx wrap ", [Block(0x1000, [opcode, 0x70]),
|
|
|
|
Block(0x0010, [0xC5, 0x10])]);
|
|
|
|
|
|
|
|
setX(cpu, 0x8F);
|
|
|
|
callNext("izx px ", [Block(0x1000, [opcode, 0x70]),
|
|
|
|
Block(0x00FF, [0xC5]),
|
|
|
|
Block(0x0000, [0x10])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For zeropage indirect,y address mode.
|
|
|
|
auto setup_addr_izy()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
info.len = 2;
|
2012-03-25 08:25:41 +00:00
|
|
|
setPC(cpu, 0x1000);
|
2012-04-03 00:58:51 +00:00
|
|
|
setA(cpu, 0x01); setX(cpu, 0x01);
|
|
|
|
|
|
|
|
info.addr = pageCrossAdd(0x10C5, 0x20);
|
|
|
|
setY(cpu, 0x20);
|
|
|
|
|
|
|
|
callNext("izy no-wrap ", [Block(0x1000, [opcode, 0x70]),
|
|
|
|
Block(0x0070, [0xC5, 0x10])]);
|
|
|
|
|
|
|
|
callNext("izy wrap ", [Block(0x1000, [opcode, 0xFF]),
|
|
|
|
Block(0x00FF, [0xC5]),
|
|
|
|
Block(0x0000, [0x10])]);
|
|
|
|
|
|
|
|
info.addr = pageCrossAdd(0x10C5, 0x50);
|
|
|
|
setY(cpu, 0x50);
|
|
|
|
callNext("izy px ", [Block(0x1000, [opcode, 0x70]),
|
|
|
|
Block(0x0070, [0xC5, 0x10])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For zeropage indirect address mode.
|
|
|
|
auto setup_addr_zpi()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
info.len = 2;
|
|
|
|
info.addr = 0x10C5;
|
2012-03-25 08:25:41 +00:00
|
|
|
setPC(cpu, 0x1000);
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
callNext("zpi no-wrap ", [Block(0x1000, [opcode, 0x70]),
|
|
|
|
Block(0x0070, [0xC5, 0x10])]);
|
|
|
|
|
|
|
|
callNext("zpi wrap ", [Block(0x1000, [opcode, 0xFF]),
|
|
|
|
Block(0x00FF, [0xC5]),
|
|
|
|
Block(0x0000, [0x10])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Splits with the SP at 0xFF, and then at 0x00 (to cover the case of
|
|
|
|
* pull operations when the stack is "empty").
|
|
|
|
*/
|
|
|
|
auto setup_pull_wrap()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setSP(cpu, 0xFF);
|
|
|
|
callNext("wrap ");
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
setSP(cpu, 0x00);
|
|
|
|
callNext("no-wrap ");
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* For pull opcodes.
|
|
|
|
*
|
|
|
|
* Splits with 0x00, 0x40, and 0x80 at the top of the stack (to
|
|
|
|
* exercise different potential combinations of N and Z).
|
|
|
|
*/
|
|
|
|
auto setup_op_pull(Reg reg)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
auto sp = pageWrapAdd(getSP(cpu), 1);
|
|
|
|
ubyte other = (info.data > 0x00 && info.data < 0x80) ? 0x00 : 0x01;
|
|
|
|
setX(cpu, other); setY(cpu, other); setA(cpu, other);
|
|
|
|
callNext("pull ", [Block(sp, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_pull_wrap(), setup_nz(), setup_data(),
|
|
|
|
TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* Splits with each possible meaningful status byte value on the
|
|
|
|
* stack, and with values with break/reserved bits set.
|
|
|
|
*/
|
|
|
|
auto setup_status()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
{
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
|
|
{
|
|
|
|
info.data = cast(ubyte)(i * 0x40 + j);
|
|
|
|
callNext(format("S %0.2X ", info.data | 0x30));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string[] msgs = ["B ", "R ", "BR "];
|
|
|
|
foreach (val; [0x10, 0x20, 0x30])
|
|
|
|
{
|
|
|
|
info.data = cast(ubyte)val;
|
|
|
|
callNext("S " ~ msgs[(info.data >> 4) - 1]);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For PLP.
|
|
|
|
auto setup_op_PLP()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
auto sp = pageWrapAdd(getSP(cpu), 1);
|
|
|
|
setStatus(cpu, ~info.data);
|
|
|
|
callNext("PLP ", [Block(sp, [info.data])]);
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return connect(setup_pull_wrap(), setup_status(), TestSetup(&setup));
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Splits with the SP at 0x00, and then at 0xFF (to cover the case of
|
|
|
|
* push operations when the stack is "full").
|
|
|
|
*/
|
|
|
|
auto setup_push_wrap()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setSP(cpu, 0x00);
|
|
|
|
callNext("wrap ");
|
|
|
|
|
|
|
|
setSP(cpu, 0xFF);
|
|
|
|
callNext("no-wrap ");
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* For push opcodes.
|
|
|
|
*
|
|
|
|
* Splits with 0x00, 0x40, and 0x80 in the appropriate register.
|
|
|
|
*/
|
|
|
|
auto setup_op_push(Reg reg)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setX(cpu, 0x01); setY(cpu, 0x01); setA(cpu, 0x01);
|
|
|
|
setReg(cpu, reg, info.data);
|
|
|
|
callNext("push ", [Block(getSP(cpu), [~info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_push_wrap(), setup_data(), TestSetup(&setup));
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* For PHP.
|
|
|
|
*
|
|
|
|
* Puts a garbage value (that does not correspond to the current
|
|
|
|
* status) just below the top of the stack.
|
|
|
|
*/
|
|
|
|
auto setup_op_PHP()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
info.data = getStatus(cpu);
|
|
|
|
callNext("PHP ", [Block(getSP(cpu), [~info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nvdzc(), setup_push_wrap(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For load opcodes.
|
|
|
|
*
|
|
|
|
* Splits with 0x00, 0x40, and 0x80 in the source memory location.
|
|
|
|
*/
|
|
|
|
auto setup_op_load(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
callNext("load ", [Block(info.addr, [info.data])]);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return connect(setup_nz(), setup_data(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// Splits with 0x00, 0x40, 0x80 in the source register.
|
|
|
|
auto setup_op_store(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mixin testCallNext;
|
|
|
|
setReg(cpu, reg, info.data);
|
|
|
|
callNext("store ", [Block(info.addr, [~info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_data(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For SAX.
|
|
|
|
auto setup_op_SAX()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
if (opcode == 0x83)
|
|
|
|
{
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("zero SAX ");
|
|
|
|
|
|
|
|
setA(cpu, 0x2B);
|
|
|
|
callNext("positive SAX ");
|
|
|
|
|
|
|
|
setA(cpu, 0x8B);
|
|
|
|
callNext("negative SAX ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
setA(cpu, 0x00); setX(cpu, 0x00);
|
|
|
|
callNext("zero SAX ");
|
|
|
|
|
|
|
|
setA(cpu, 0x10); setX(cpu, 0x01);
|
|
|
|
callNext("zero SAX ");
|
|
|
|
|
|
|
|
setA(cpu, 0x0B); setX(cpu, 0x0D);
|
|
|
|
callNext("positive SAX ");
|
|
|
|
|
|
|
|
setA(cpu, 0x8B); setX(cpu, 0x8D);
|
|
|
|
callNext("negative SAX ");
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For STZ.
|
|
|
|
auto setup_op_STZ()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0;
|
|
|
|
callNext("STZ ", [Block(info.addr, [~info.data])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// setup_bit_val, puts in memory;
|
|
|
|
auto setup_bit_data()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("N1 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
info.data = 0x40;
|
|
|
|
callNext("V1 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
info.data = 0xC0;
|
|
|
|
callNext("N1V1 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
info.data = 0x20;
|
|
|
|
callNext("N0V0 ", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For BIT.
|
|
|
|
auto setup_op_BIT()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, info.data);
|
|
|
|
callNext("BIT non-zero ");
|
|
|
|
|
|
|
|
setA(cpu, ~info.data);
|
|
|
|
callNext("BIT zero");
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), setup_flag(Flag.V), setup_bit_data(),
|
|
|
|
TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For TRB.
|
|
|
|
auto setup_op_TRB()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0); setX(cpu, 0xFF); setY(cpu, 0xFF);
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("TRB Z1 1 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x40); setX(cpu, 0x00); setY(cpu, 0x00);
|
|
|
|
info.data = 0x40;
|
|
|
|
callNext("TRB Z0 0 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x40); setX(cpu, 0x00); setY(cpu, 0x00);
|
|
|
|
info.data = 0x41;
|
|
|
|
callNext("TRB Z0 1 ", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For TSB.
|
|
|
|
auto setup_op_TSB()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x01); setX(cpu, 0x00); setY(cpu, 0x00);
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("TSB Z1 1 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x02); setX(cpu, 0x00); setY(cpu, 0x00);
|
|
|
|
info.data = 0x02;
|
|
|
|
callNext("TSB Z0 2 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x30); setX(cpu, 0x00); setY(cpu, 0x00);
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("TSB Z1 B0 ", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x00); setX(cpu, 0xFF); setY(cpu, 0xFF);
|
|
|
|
info.data = 0x02;
|
|
|
|
callNext("TSB Z1 2 ", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For BRK.
|
|
|
|
auto setup_op_BRK()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.addr = 0xFE55;
|
|
|
|
setPC(cpu, 0xFD00);
|
|
|
|
ushort ret = 0xFD02;
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, -1);
|
|
|
|
auto sp2 = pageWrapAdd(sp, -2);
|
|
|
|
info.data = getStatus(cpu);
|
|
|
|
callNext("BRK ", [Block(0xFD00, [opcode]),
|
|
|
|
Block(sp, [~addrHi(ret)]),
|
|
|
|
Block(sp1, [~addrLo(ret)]),
|
|
|
|
Block(sp2, [~info.data]),
|
|
|
|
Block(0xFFFE, [0x55, 0xFE])]);
|
|
|
|
|
|
|
|
}
|
|
|
|
return connect(setup_nvdzc(), setup_push_wrap(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto setup_RTx(bool isRTI)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
string name = (isRTI ? "RTI " : "RTS ");
|
|
|
|
info.addr = 0x1211;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, (isRTI ? 1 : 0));
|
|
|
|
auto sp2 = pageWrapAdd(sp, (isRTI ? 2 : 1));
|
|
|
|
auto sp3 = pageWrapAdd(sp, (isRTI ? 3 : 2));
|
|
|
|
callNext(name, [Block(0x1000, [opcode])] ~
|
|
|
|
(isRTI ? [Block(sp1, [info.data])] : []) ~
|
|
|
|
[Block(sp2, [addrLo(info.addr)]),
|
|
|
|
Block(sp3, [addrHi(info.addr)])]);
|
|
|
|
}
|
|
|
|
return connect(setup_pull_wrap(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For RTI.
|
|
|
|
auto setup_op_RTI()
|
|
|
|
{
|
|
|
|
return connect(setup_status(), setup_RTx(true));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For RTS.
|
|
|
|
auto setup_op_RTS()
|
|
|
|
{
|
|
|
|
return setup_RTx(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For JMP inx.
|
|
|
|
auto setup_op_JMP_inx()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.addr = 0x1234;
|
|
|
|
info.len = 3;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
|
|
|
|
setX(cpu, 0x20);
|
|
|
|
callNext("JMP inx A ", [Block(0x1000, [opcode, 0xC5, 0x10]),
|
|
|
|
Block(0x10E5, [0x34, 0x12])]);
|
|
|
|
|
|
|
|
setX(cpu, 0x40);
|
|
|
|
callNext("JMP inx B ", [Block(0x1000, [opcode, 0xC5, 0x10]),
|
|
|
|
Block(0x1105, [0x34, 0x12])]);
|
|
|
|
|
|
|
|
setX(cpu, 0x30);
|
|
|
|
callNext("JMP inx C ", [Block(0x1000, [opcode, 0xCF, 0x10]),
|
|
|
|
Block(0x10FF, [0x34, 0x12])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For JMP ind.
|
|
|
|
auto setup_op_JMP_ind(bool cmos)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.addr = 0x1234;
|
|
|
|
info.len = 3;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
setX(cpu, 0x40); setY(cpu, 0x40); setA(cpu, 0x40);
|
|
|
|
|
|
|
|
callNext("JMP ind px ", [Block(0x1000, [opcode, 0xFF, 0x11])] ~
|
|
|
|
(cmos ? [Block(0x11FF, [0x34, 0x12])]
|
|
|
|
: [Block(0x11FF, [0x34]),
|
|
|
|
Block(0x1100, [0x12])]));
|
|
|
|
|
|
|
|
callNext("JMP ind no px ", [Block(0x1000, [opcode, 0xC5, 0x10]),
|
|
|
|
Block(0x10C5, [0x34, 0x12])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For JSR.
|
|
|
|
auto setup_op_JSR()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.addr = 0x10C5;
|
|
|
|
setPC(cpu, 0x1000);
|
|
|
|
ushort ret = 0x1002;
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, 1);
|
|
|
|
callNext("JSR ", [Block(0x1000, [opcode, 0xC5, 0x10]),
|
|
|
|
Block(sp, [~addrHi(ret)]),
|
|
|
|
Block(sp, [~addrLo(ret)])]);
|
|
|
|
}
|
|
|
|
return connect(setup_push_wrap(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For CMOS opcode 5C.
|
|
|
|
auto setup_op_5C()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.len = 3;
|
|
|
|
setPC(cpu, 0xFD00);
|
|
|
|
callNext("NOP8 ", [Block(0xFD00, [0x5C, 0x72])]);
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto setup_dec_data()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("zero ");
|
|
|
|
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("negative ");
|
|
|
|
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("positive ");
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For DEX, DEY, DEA.
|
|
|
|
auto setup_op_DEC_reg(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setX(cpu, 0x40); setY(cpu, 0x40); setA(cpu, 0x40);
|
|
|
|
setReg(cpu, reg, info.data);
|
|
|
|
callNext("DEC r ", [Block(0x1000, [opcode])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), setup_dec_data(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For DEC.
|
|
|
|
auto setup_op_DEC()
|
|
|
|
{
|
|
|
|
return setup_rmw(false, "DEC ", setup_dec_data());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto setup_inc_data()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0xFF;
|
|
|
|
callNext("zero ");
|
|
|
|
|
|
|
|
info.data = 0x7F;
|
|
|
|
callNext("negative ");
|
|
|
|
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("positive ");
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For INX, INY, INA
|
|
|
|
auto setup_op_INC_reg(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setX(cpu, 0x40); setY(cpu, 0x40); setA(cpu, 0x40);
|
|
|
|
setReg(cpu, reg, info.data);
|
|
|
|
callNext("INC r ", [Block(0x1000, [opcode])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), setup_inc_data(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For INC.
|
|
|
|
auto setup_op_INC()
|
|
|
|
{
|
|
|
|
return setup_rmw(false, "INC ", setup_inc_data());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto setup_rmw(bool isAcc, string name, TestSetup data_setup)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
if (isAcc)
|
|
|
|
{
|
|
|
|
setA(cpu, info.data); setX(cpu, 0x10); setY(cpu, 0x10);
|
|
|
|
callNext(name ~ "a ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
callNext(name, [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), data_setup, TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For ROL.
|
|
|
|
auto setup_op_ROL(bool isAcc)
|
|
|
|
{
|
|
|
|
return setup_rmw(isAcc, "ROL ", setup_data());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto setup_asl_data()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("zero ");
|
|
|
|
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("zero carry ");
|
|
|
|
|
|
|
|
info.data = 0x40;
|
|
|
|
callNext("negative ");
|
|
|
|
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("positive ");
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For ASL.
|
|
|
|
auto setup_op_ASL(bool isAcc)
|
|
|
|
{
|
|
|
|
return setup_rmw(isAcc, "ASL ", setup_asl_data());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto setup_right_data()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("0x01 ");
|
|
|
|
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("0x80 ");
|
|
|
|
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("0x00 ");
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For ROR.
|
|
|
|
auto setup_op_ROR(bool isAcc)
|
|
|
|
{
|
|
|
|
return setup_rmw(isAcc, "ROR ", setup_right_data());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For LSR.
|
|
|
|
auto setup_op_LSR(bool isAcc)
|
|
|
|
{
|
|
|
|
return setup_rmw(isAcc, "LSR ", setup_right_data());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For SLO.
|
|
|
|
auto setup_op_SLO()
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("acc 0x00 ");
|
|
|
|
|
|
|
|
setA(cpu, 0xFF);
|
|
|
|
callNext("acc 0xFF ");
|
|
|
|
|
|
|
|
setA(cpu, 0x20);
|
|
|
|
callNext("acc 0x20 ");
|
|
|
|
}
|
|
|
|
return connect(TestSetup(&setup),
|
2012-04-12 03:32:16 +00:00
|
|
|
setup_rmw(false, "SLO ", setup_asl_data()));
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// For RLA.
|
|
|
|
auto setup_op_RLA()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("acc 0x00 ");
|
|
|
|
|
|
|
|
setA(cpu, 0xFF);
|
|
|
|
callNext("acc 0xFF ");
|
|
|
|
}
|
|
|
|
return connect(TestSetup(&setup),
|
|
|
|
setup_rmw(false, "RLA ", setup_data()));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For SRE.
|
|
|
|
auto setup_op_SRE()
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("acc 0x00 ");
|
|
|
|
|
|
|
|
setA(cpu, 0xFF);
|
|
|
|
callNext("acc 0xFF ");
|
|
|
|
}
|
|
|
|
return connect(TestSetup(&setup),
|
2012-04-12 03:32:16 +00:00
|
|
|
setup_rmw(false, "SRE ", setup_right_data()));
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For ADC.
|
|
|
|
*
|
|
|
|
* This opcode is extensively tested elsewhere.
|
|
|
|
*/
|
|
|
|
auto setup_op_ADC(bool cmos)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x99);
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("ADC 1", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x5B);
|
|
|
|
info.data = 0x46;
|
|
|
|
callNext("ADC 2", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x50);
|
|
|
|
info.data = 0x60;
|
|
|
|
callNext("ADC 3", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x90);
|
|
|
|
info.data = 0x90;
|
|
|
|
callNext("ADC 4", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x90);
|
|
|
|
info.data = 0x0F;
|
|
|
|
callNext("ADC 5", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), setup_flag(Flag.C), setup_flag(Flag.D),
|
|
|
|
TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For RRA.
|
|
|
|
auto setup_op_RRA()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("acc 0x00 ");
|
|
|
|
|
|
|
|
setA(cpu, 0x10);
|
|
|
|
callNext("acc 0x10 ");
|
|
|
|
|
|
|
|
setA(cpu, 0xBF);
|
|
|
|
callNext("acc 0xBF ");
|
|
|
|
}
|
|
|
|
|
|
|
|
return connect(TestSetup(&setup), setup_flag(Flag.C), setup_flag(Flag.D),
|
|
|
|
setup_rmw(false, "RRA ", setup_right_data()));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For SBC.
|
|
|
|
*
|
|
|
|
* This opcode is extensively tested elsewhere.
|
|
|
|
*/
|
|
|
|
auto setup_op_SBC(bool cmos)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("SBC 1", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x46);
|
|
|
|
info.data = 0x12;
|
|
|
|
callNext("SBC 2", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x50);
|
|
|
|
info.data = 0x60;
|
|
|
|
callNext("SBC 3", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x32);
|
|
|
|
info.data = 0x02;
|
|
|
|
callNext("SBC 4", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x90);
|
|
|
|
info.data = 0x0F;
|
|
|
|
callNext("SBC 5", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), setup_flag(Flag.C), setup_flag(Flag.D),
|
|
|
|
TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For ISC.
|
|
|
|
auto setup_op_ISC()
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("acc 0x00 ");
|
|
|
|
|
|
|
|
setA(cpu, 0x10);
|
|
|
|
callNext("acc 0x10 ");
|
|
|
|
|
|
|
|
setA(cpu, 0xBF);
|
|
|
|
callNext("acc 0xBF ");
|
|
|
|
}
|
|
|
|
|
|
|
|
return connect(TestSetup(&setup), setup_flag(Flag.C), setup_flag(Flag.D),
|
2012-04-12 03:32:16 +00:00
|
|
|
setup_rmw(false, "ISC ", setup_inc_data()));
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For CMP, CPY, CPX.
|
|
|
|
auto setup_op_cmp(Reg reg)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setReg(cpu, reg, 0x10);
|
|
|
|
info.data = 0x20;
|
|
|
|
callNext("compare <", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setReg(cpu, reg, 0x10);
|
|
|
|
info.data = 0x10;
|
|
|
|
callNext("compare =", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setReg(cpu, reg, 0x20);
|
|
|
|
info.data = 0x10;
|
|
|
|
callNext("compare >", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(), TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For DCP.
|
|
|
|
auto setup_op_DCP()
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
callNext("acc 0x00 ");
|
|
|
|
|
|
|
|
setA(cpu, 0x10);
|
|
|
|
callNext("acc 0x10 ");
|
|
|
|
|
|
|
|
setA(cpu, 0xBF);
|
|
|
|
callNext("acc 0xBF ");
|
|
|
|
}
|
|
|
|
|
|
|
|
return connect(TestSetup(&setup), setup_flag(Flag.C),
|
2012-04-12 03:32:16 +00:00
|
|
|
setup_rmw(false, "DCP ", setup_dec_data()));
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For ORA.
|
|
|
|
auto setup_op_ORA()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x00);
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("ORA zero", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x10);
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("ORA positive", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x80);
|
|
|
|
info.data = 0x00;
|
|
|
|
callNext("ORA negative", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(),TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For AND.
|
|
|
|
auto setup_op_AND()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0x10);
|
|
|
|
info.data = 0x01;
|
|
|
|
callNext("AND zero", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x0B);
|
|
|
|
info.data = 0x0D;
|
|
|
|
callNext("AND positive", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x80);
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("AND negative", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(),TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For EOR.
|
|
|
|
auto setup_op_EOR()
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
setA(cpu, 0xFF);
|
|
|
|
info.data = 0xFF;
|
|
|
|
callNext("EOR zero", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x81);
|
|
|
|
info.data = 0x82;
|
|
|
|
callNext("EOR positive", [Block(info.addr, [info.data])]);
|
|
|
|
|
|
|
|
setA(cpu, 0x40);
|
|
|
|
info.data = 0x80;
|
|
|
|
callNext("EOR negative", [Block(info.addr, [info.data])]);
|
|
|
|
}
|
|
|
|
return connect(setup_nz(),TestSetup(&setup));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A range over the differences in two TestMemory structs. Each
|
|
|
|
* element is an address with up to eight bytes of subsequent data
|
|
|
|
* from the two structs.
|
|
|
|
*/
|
|
|
|
struct MemDiff
|
|
|
|
{
|
|
|
|
|
|
|
|
static struct DiffBlock
|
|
|
|
{
|
|
|
|
ushort base;
|
|
|
|
const(ubyte[]) a;
|
|
|
|
const(ubyte[]) b;
|
|
|
|
}
|
|
|
|
|
|
|
|
const(TestMemory*) a, b;
|
|
|
|
int i;
|
|
|
|
bool _empty, _emptyChecked;
|
|
|
|
|
|
|
|
this(const ref TestMemory a, const ref TestMemory b)
|
|
|
|
{
|
|
|
|
assert(a.data2_base == b.data2_base && a.data2_max == b.data2_max);
|
|
|
|
this.a = &a; this.b = &b;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto front()
|
|
|
|
{
|
|
|
|
assert(_emptyChecked && !_empty);
|
|
|
|
if (i < 0x200)
|
|
|
|
{
|
|
|
|
auto extent = min(8, 0x200 - i);
|
|
|
|
return DiffBlock(cast(ushort)i,
|
|
|
|
a.data1[i..i+extent].dup,
|
|
|
|
b.data1[i..i+extent].dup);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto extent = min(8, a.data2_max - i);
|
|
|
|
auto di = i - a.data2_base;
|
|
|
|
return DiffBlock(cast(ushort)i,
|
|
|
|
a.data2[di..di+extent].dup,
|
|
|
|
b.data2[di..di+extent].dup);
|
|
|
|
}
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void popFront()
|
|
|
|
{
|
|
|
|
assert(_emptyChecked && !_empty);
|
|
|
|
if (i < 0x200)
|
|
|
|
i += min(8, 0x200 - i);
|
|
|
|
else
|
|
|
|
i += min(8, a.data2_max - i);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool empty()
|
|
|
|
{
|
|
|
|
_emptyChecked = true;
|
|
|
|
while (i < 0x200)
|
|
|
|
{
|
|
|
|
if (a.data1[i] != b.data1[i]) { _empty = false; return false; }
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (i < a.data2_base) i = a.data2_base;
|
|
|
|
while (i < a.data2_max)
|
|
|
|
{
|
|
|
|
auto di = i - a.data2_base;
|
|
|
|
if (a.data2[di] != b.data2[di]) { _empty = false; return false; }
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
_empty = true; return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct Expected
|
|
|
|
{
|
|
|
|
CpuInfo cpu;
|
|
|
|
TestMemory mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
alias void delegate(ref Expected, const OpInfo)
|
|
|
|
testexpect;
|
|
|
|
|
|
|
|
|
|
|
|
// Expects nothing to have changed.
|
|
|
|
auto expect_none()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For opcodes which modify a register and set N/Z.
|
|
|
|
void expect_basic(Reg reg, ubyte val, ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
setReg(cpu, reg, val);
|
|
|
|
setNZ(cpu, val);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For flag opcodes.
|
|
|
|
auto expect_flag(Flag f, bool val)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
updateFlag(cpu, f, val);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For NOP.
|
|
|
|
auto expect_NOP()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected) { incPC(cpu, info.len); }
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For register transfer opcodes.
|
|
|
|
auto expect_transfer(Reg source, Reg dest)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = getReg(expected.cpu, source);
|
|
|
|
expect_basic(dest, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For push opcodes.
|
|
|
|
auto expect_push(Reg reg)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
mem[getSP(cpu)] = getReg(cpu, reg);
|
|
|
|
decSP(cpu);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For PHP.
|
|
|
|
auto expect_PHP()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
mem[getSP(cpu)] = getStatus(cpu);
|
|
|
|
decSP(cpu);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For pull opcodes.
|
|
|
|
auto expect_pull(Reg reg)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
incSP(cpu);
|
|
|
|
auto val = mem[getSP(cpu)];
|
|
|
|
expect_basic(reg, val, expected, info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For PLP.
|
|
|
|
auto expect_PLP()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
incSP(cpu);
|
|
|
|
setStatus(cpu, mem[getSP(cpu)]);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For TSX.
|
|
|
|
auto expect_TSX()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
ubyte val = cast(ubyte)getSP(expected.cpu);
|
|
|
|
expect_basic(Reg.X, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For TXS.
|
|
|
|
auto expect_TXS()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
auto val = getX(cpu);
|
|
|
|
setSP(cpu, val);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For load opcodes.
|
|
|
|
auto expect_load(Reg reg)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = expected.mem[info.addr];
|
|
|
|
expect_basic(reg, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For LAX (except immediate).
|
|
|
|
auto expect_LAX()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
expect_load(Reg.A)(expected, info);
|
|
|
|
with(expected) { setX(cpu, getA(cpu)); }
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For store opcodes.
|
|
|
|
auto expect_store(Reg reg)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
mem[info.addr] = getReg(cpu, reg);
|
|
|
|
incPC(cpu, info.len);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For SAX.
|
|
|
|
auto expect_SAX()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
mem[info.addr] = getA(cpu) & getX(cpu);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For STZ.
|
|
|
|
auto expect_STZ()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
mem[info.addr] = 0;
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For branch opcodes.
|
|
|
|
auto expect_branch()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected) { setPC(cpu, info.addr); }
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For BIT.
|
|
|
|
auto expect_BIT()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
updateFlag(cpu, Flag.N, (info.data & 0x80) != 0);
|
|
|
|
updateFlag(cpu, Flag.V, (info.data & 0x40) != 0);
|
|
|
|
updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For BIT.
|
|
|
|
auto expect_BIT_imm()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For TRB.
|
|
|
|
auto expect_TRB()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-23 16:14:15 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0);
|
|
|
|
mem[info.addr] = ~getA(cpu) & info.data;
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
2012-03-23 16:14:15 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
auto expect_TSB()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
updateFlag(cpu, Flag.Z, (info.data & getA(cpu)) == 0);
|
|
|
|
mem[info.addr] = getA(cpu) | info.data;
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-23 16:14:15 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For BRK.
|
|
|
|
auto expect_BRK()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
ushort ret = cast(ushort)(getPC(cpu) + 2);
|
|
|
|
mem[getSP(cpu)] = addrHi(ret);
|
|
|
|
decSP(cpu);
|
|
|
|
mem[getSP(cpu)] = addrLo(ret);
|
|
|
|
decSP(cpu);
|
|
|
|
mem[getSP(cpu)] = info.data;
|
|
|
|
decSP(cpu);
|
|
|
|
setPC(cpu, info.addr);
|
|
|
|
setFlag(cpu, Flag.I);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For RTI.
|
|
|
|
auto expect_RTI()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
setStatus(cpu, info.data);
|
|
|
|
setPC(cpu, info.addr);
|
|
|
|
incSP(cpu, 3);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For RTS.
|
|
|
|
auto expect_RTS()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
setPC(cpu, info.addr);
|
|
|
|
incSP(cpu, 2);
|
|
|
|
incPC(cpu, 1);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
return &expect;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// For JMP.
|
|
|
|
auto expect_JMP()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
setPC(cpu, info.addr);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For JSR.
|
|
|
|
auto expect_JSR()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
auto ret = cast(ushort)(getPC(cpu) + 2);
|
|
|
|
setPC(cpu, info.addr);
|
|
|
|
mem[getSP(cpu)] = addrHi(ret);
|
|
|
|
decSP(cpu);
|
|
|
|
mem[getSP(cpu)] = addrLo(ret);
|
|
|
|
decSP(cpu);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
void expect_rmw(bool isAcc, ubyte val, ref Expected expected,
|
|
|
|
const OpInfo info)
|
|
|
|
{
|
|
|
|
if (isAcc)
|
|
|
|
expect_basic(Reg.A, val, expected, info);
|
|
|
|
else
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
mem[info.addr] = val;
|
|
|
|
setNZ(cpu, val);
|
|
|
|
incPC(cpu, info.len);
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For DEX, DEY, DEA.
|
|
|
|
auto expect_DEC_reg(Reg reg)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data - 1);
|
|
|
|
expect_basic(reg, val, expected, info);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For DEC.
|
|
|
|
auto expect_DEC()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data - 1);
|
|
|
|
expect_rmw(false, val, expected, info);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For INX, INY, INA.
|
|
|
|
auto expect_INC_reg(Reg reg)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data + 1);
|
|
|
|
expect_basic(reg, val, expected, info);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For INC.
|
|
|
|
auto expect_INC()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data + 1);
|
|
|
|
expect_rmw(false, val, expected, info);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For ROL.
|
|
|
|
auto expect_ROL(bool isAcc)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto carry = (getFlag(expected.cpu, Flag.C) ? 1 : 0);
|
|
|
|
auto val = cast(ubyte)(info.data << 1 | carry);
|
|
|
|
updateFlag(expected.cpu, Flag.C, (info.data > 0x7F));
|
|
|
|
|
|
|
|
expect_rmw(isAcc, val, expected, info);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For ASL.
|
|
|
|
auto expect_ASL(bool isAcc)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data << 1);
|
|
|
|
updateFlag(expected.cpu, Flag.C, (info.data > 0x7F));
|
|
|
|
|
|
|
|
expect_rmw(isAcc, val, expected, info);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For ROR.
|
|
|
|
auto expect_ROR(bool isAcc)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto carry = (getFlag(expected.cpu, Flag.C) ? 0x80 : 0);
|
|
|
|
auto val = cast(ubyte)(info.data >> 1 | carry);
|
|
|
|
updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0));
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
expect_rmw(isAcc, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For LSR.
|
|
|
|
auto expect_LSR(bool isAcc)
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data >> 1);
|
|
|
|
updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0));
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
expect_rmw(isAcc, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For SLO.
|
|
|
|
auto expect_SLO()
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data << 1);
|
|
|
|
updateFlag(expected.cpu, Flag.C, (info.data > 0x7F));
|
|
|
|
setA(expected.cpu, getA(expected.cpu) | val);
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
expect_rmw(false, val, expected, info);
|
2012-04-12 02:04:18 +00:00
|
|
|
setNZ(expected.cpu, getA(expected.cpu));
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For RLA.
|
|
|
|
auto expect_RLA()
|
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto carry = (getFlag(expected.cpu, Flag.C) ? 1 : 0);
|
|
|
|
auto val = cast(ubyte)(info.data << 1 | carry);
|
|
|
|
updateFlag(expected.cpu, Flag.C, (info.data > 0x7F));
|
|
|
|
setA(expected.cpu, getA(expected.cpu) & val);
|
|
|
|
|
|
|
|
expect_rmw(false, val, expected, info);
|
2012-04-12 02:04:18 +00:00
|
|
|
setNZ(expected.cpu, getA(expected.cpu));
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For SRE.
|
|
|
|
auto expect_SRE()
|
2012-04-03 00:58:51 +00:00
|
|
|
{
|
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
auto val = cast(ubyte)(info.data >> 1);
|
|
|
|
updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0));
|
|
|
|
setA(expected.cpu, getA(expected.cpu) ^ val);
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
expect_rmw(false, val, expected, info);
|
2012-04-12 02:04:18 +00:00
|
|
|
setNZ(expected.cpu, getA(expected.cpu));
|
2012-04-03 00:58:51 +00:00
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* Slightly cheating--this is copied from the original implementation,
|
|
|
|
* but it was independently tested (see test_decimal.d).
|
|
|
|
*/
|
|
|
|
void expect_add(bool cmos, ubyte val, ref Expected expected)
|
|
|
|
{
|
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
ubyte acc = getA(cpu);
|
|
|
|
auto d = getFlag(cpu, Flag.D);
|
|
|
|
auto c = getFlag(cpu, Flag.C);
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
if (!d)
|
|
|
|
{
|
|
|
|
uint sum = acc + val + c;
|
|
|
|
updateFlag(cpu, Flag.V,
|
|
|
|
(!((acc ^ val) & 0x80)) && ((val ^ sum) & 0x80));
|
|
|
|
updateFlag(cpu, Flag.C, (sum > 0xFF));
|
|
|
|
setNZ(cpu, cast(ubyte)sum);
|
|
|
|
setA(cpu, cast(ubyte)sum);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int a = acc;
|
|
|
|
int al = (a & 0x0F) + (val & 0x0F) + c;
|
|
|
|
if (al >= 0x0A)
|
|
|
|
al = ((al + 0x06) & 0x0F) + 0x10;
|
|
|
|
a = (a & 0xF0) + (val & 0xF0) + al;
|
|
|
|
|
|
|
|
if (!cmos)
|
|
|
|
{
|
|
|
|
updateFlag(cpu, Flag.N, (a & 0xFF) > 0x7F);
|
|
|
|
updateFlag(cpu, Flag.Z, (cast(ubyte)(acc + val + c)) == 0);
|
|
|
|
}
|
|
|
|
updateFlag(cpu, Flag.V,
|
|
|
|
(!((acc ^ val) & 0x80)) && ((val ^ a) & 0x80));
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
if (a >= 0xA0)
|
|
|
|
a = a + 0x60;
|
|
|
|
updateFlag(cpu, Flag.C, (a >= 0x100));
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
setA(cpu, cast(ubyte)a);
|
|
|
|
if (cmos)
|
|
|
|
setNZ(cpu, cast(ubyte)a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For ADC.
|
|
|
|
auto expect_ADC(bool cmos)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected) { incPC(cpu, info.len); }
|
|
|
|
expect_add(cmos, info.data, expected);
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
2012-04-03 00:58:51 +00:00
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For RRA.
|
|
|
|
auto expect_RRA()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto carry = (getFlag(expected.cpu, Flag.C) ? 0x80 : 0);
|
|
|
|
auto val = cast(ubyte)(info.data >> 1 | carry);
|
|
|
|
updateFlag(expected.cpu, Flag.C, ((info.data & 0x01) != 0));
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
expect_rmw(false, val, expected, info);
|
|
|
|
expect_add(false, val, expected);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
/*
|
|
|
|
* Slightly cheating--this is copied from the original implementation,
|
|
|
|
* but it was independently tested (see test_decimal.d).
|
|
|
|
*/
|
|
|
|
void expect_sub(bool cmos, ubyte val, ref Expected expected)
|
|
|
|
{
|
|
|
|
with(expected)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
ubyte acc = getA(cpu);
|
|
|
|
auto d = getFlag(cpu, Flag.D);
|
|
|
|
auto c = getFlag(cpu, Flag.C);
|
2012-03-25 08:25:41 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
if (!d)
|
|
|
|
{
|
|
|
|
uint diff = acc - val - (c ? 0 : 1);
|
|
|
|
updateFlag(cpu, Flag.V,
|
|
|
|
((acc ^ diff) & 0x80) && ((acc ^ val) & 0x80));
|
|
|
|
updateFlag(cpu, Flag.C, (diff < 0x100));
|
|
|
|
setA(cpu, cast(ubyte)diff);
|
|
|
|
setNZ(cpu, cast(ubyte)diff);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!cmos)
|
|
|
|
{
|
|
|
|
int a = acc;
|
|
|
|
int al = (a & 0x0F) - (val & 0x0F) - !c;
|
|
|
|
if (al < 0)
|
|
|
|
al = ((al - 0x06) & 0x0F) - 0x10;
|
|
|
|
a = (a & 0xF0) - (val & 0xF0) + al;
|
|
|
|
if (a < 0)
|
|
|
|
a = a - 0x60;
|
|
|
|
|
|
|
|
uint diff = acc - val - !c;
|
|
|
|
updateFlag(cpu, Flag.V,
|
|
|
|
((acc ^ diff) & 0x80) && ((acc ^ val) & 0x80));
|
|
|
|
updateFlag(cpu, Flag.C, (diff < 0x100));
|
|
|
|
setNZ(cpu, cast(ubyte)diff);
|
|
|
|
|
|
|
|
setA(cpu, cast(ubyte)a);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int a = acc;
|
|
|
|
int al = (a & 0x0F) - (val & 0x0F) - !c;
|
|
|
|
a = a - val - !c;
|
|
|
|
if (a < 0)
|
|
|
|
a = a - 0x60;
|
|
|
|
if (al < 0)
|
|
|
|
a = a - 0x06;
|
|
|
|
|
|
|
|
uint diff = acc - val - !c;
|
|
|
|
updateFlag(cpu, Flag.V,
|
|
|
|
((acc ^ diff) & 0x80) && ((acc ^ val) & 0x80));
|
|
|
|
updateFlag(cpu, Flag.C, (diff < 0x100));
|
|
|
|
setNZ(cpu, cast(ubyte)a);
|
|
|
|
setA(cpu, cast(ubyte)a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
// For SBC.
|
|
|
|
auto expect_SBC(bool cmos)
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected) { incPC(cpu, info.len); }
|
|
|
|
expect_sub(cmos, info.data, expected);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For ISC.
|
|
|
|
auto expect_ISC()
|
2012-03-25 08:25:41 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = cast(ubyte)(info.data + 1);
|
|
|
|
expect_rmw(false, val, expected, info);
|
|
|
|
expect_sub(false, val, expected);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-25 08:25:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
void expect_compare(Reg reg, ubyte val, ref Expected expected)
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
with(expected)
|
|
|
|
{
|
|
|
|
auto r = getReg(cpu, reg);
|
|
|
|
updateFlag(cpu, Flag.C, (r >= val));
|
|
|
|
setNZ(cpu, cast(ubyte)(r - val));
|
|
|
|
}
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For CMP, CPX, CPY.
|
|
|
|
auto expect_cmp(Reg reg)
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
with(expected) { incPC(cpu, info.len); }
|
|
|
|
expect_compare(reg, info.data, expected);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
2012-04-12 03:32:16 +00:00
|
|
|
// For DCP.
|
|
|
|
auto expect_DCP()
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = cast(ubyte)(info.data - 1);
|
|
|
|
expect_rmw(false, val, expected, info);
|
|
|
|
expect_compare(Reg.A, val, expected);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For ORA.
|
|
|
|
auto expect_ORA()
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = cast(ubyte)(info.data | getA(expected.cpu));
|
|
|
|
expect_basic(Reg.A, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For AND.
|
|
|
|
auto expect_AND()
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = cast(ubyte)(info.data & getA(expected.cpu));
|
|
|
|
expect_basic(Reg.A, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
|
|
|
|
// For EOR.
|
|
|
|
auto expect_EOR()
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
void expect(ref Expected expected, const OpInfo info)
|
|
|
|
{
|
|
|
|
auto val = cast(ubyte)(info.data ^ getA(expected.cpu));
|
|
|
|
expect_basic(Reg.A, val, expected, info);
|
|
|
|
}
|
|
|
|
return &expect;
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Associates opcodes with test setup functions.
|
|
|
|
string getMemSetup(T)()
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
string[] tmp1 = new string[256], tmp2 = new string[256],
|
|
|
|
tmp3 = new string[256];
|
|
|
|
tmp1[] = " setup_addr = setup_none();\n";
|
|
|
|
tmp2[] = " setup_test = setup_none();\n";
|
|
|
|
tmp3[] = " expect = expect_none();\n";
|
2012-03-31 19:44:23 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
void get_addr(const(ubyte[]) list, string name, string arg = "")
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
|
|
|
foreach(op; list)
|
2012-04-03 00:58:51 +00:00
|
|
|
tmp1[op] = "setup_addr = setup_addr_" ~ name ~ "(" ~ arg ~ ");\n";
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
void get_test(const(ubyte[]) list, string name, string arg = "")
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
|
|
|
foreach(op; list)
|
2012-04-03 00:58:51 +00:00
|
|
|
tmp2[op] = "setup_test = setup_op_" ~ name ~ "(" ~ arg ~ ");\n";
|
|
|
|
}
|
2012-03-31 19:44:23 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
void get_expect(const(ubyte[]) list, string name, string arg = "")
|
|
|
|
{
|
|
|
|
foreach(op; list)
|
|
|
|
tmp3[op] = "expect = expect_" ~ name ~ "(" ~ arg ~ ");\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
void get_both(const(ubyte[]) list, string name, string arg = "")
|
2012-03-31 19:44:23 +00:00
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
get_test(list, name, arg);
|
|
|
|
get_expect(list, name, arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
get_addr(REG_OPS!T, "implied");
|
|
|
|
get_addr(PUSH_OPS!T, "implied");
|
|
|
|
get_addr(PULL_OPS!T, "implied");
|
|
|
|
get_addr(BRANCH_OPS!T, "branch", "isCMOS!T");
|
|
|
|
get_addr(IMM_OPS!T, "imm");
|
|
|
|
get_addr(ZPG_OPS!T, "zpg");
|
|
|
|
get_addr(ABS_OPS!T, "abs");
|
|
|
|
get_addr([0x4C], "abs");
|
|
|
|
get_addr(ZPX_OPS!T, "zpxy", "Reg.X");
|
|
|
|
get_addr(ZPY_OPS!T, "zpxy", "Reg.Y");
|
|
|
|
get_addr(ABX_OPS!T, "abxy", "Reg.X");
|
|
|
|
get_addr(ABY_OPS!T, "abxy", "Reg.Y");
|
|
|
|
get_addr(IZX_OPS!T, "izx");
|
|
|
|
get_addr(IZY_OPS!T, "izy");
|
|
|
|
|
|
|
|
get_expect([0x18], "flag", "Flag.C, false");
|
|
|
|
get_expect([0x38], "flag", "Flag.C, true");
|
|
|
|
get_expect([0xD8], "flag", "Flag.D, false");
|
|
|
|
get_expect([0xF8], "flag", "Flag.D, true");
|
|
|
|
get_expect([0x58], "flag", "Flag.I, false");
|
|
|
|
get_expect([0x78], "flag", "Flag.I, true");
|
|
|
|
get_expect([0xB8], "flag", "Flag.V, false");
|
|
|
|
get_both([0x08], "PHP");
|
|
|
|
get_both([0x28], "PLP");
|
|
|
|
get_both([0x48], "push", "Reg.A");
|
|
|
|
get_both([0x68], "pull", "Reg.A");
|
|
|
|
get_both([0x8A], "transfer", "Reg.X, Reg.A");
|
|
|
|
get_both([0x98], "transfer", "Reg.Y, Reg.A");
|
|
|
|
get_both([0xA8], "transfer", "Reg.A, Reg.Y");
|
|
|
|
get_both([0xAA], "transfer", "Reg.A, Reg.X");
|
|
|
|
get_test([0x9A, 0xBA], "stack_xfer");
|
|
|
|
get_expect([0x9A], "TXS");
|
|
|
|
get_expect([0xBA], "TSX");
|
|
|
|
get_both([0xA1, 0xA5, 0xA9, 0xAD, 0xB1, 0xB5, 0xB9, 0xBD],
|
|
|
|
"load", "Reg.A");
|
|
|
|
get_both([0xA2, 0xA6, 0xAE, 0xB6, 0xBE], "load", "Reg.X");
|
|
|
|
get_both([0xA0, 0xA4, 0xAC, 0xB4, 0xBC], "load", "Reg.Y");
|
|
|
|
get_both([0x81, 0x85, 0x8D, 0x91, 0x95, 0x99, 0x9D], "store", "Reg.A");
|
|
|
|
get_both([0x86, 0x8E, 0x96], "store", "Reg.X");
|
|
|
|
get_both([0x84, 0x8C, 0x94], "store", "Reg.Y");
|
|
|
|
get_expect(BRANCH_OPS!T, "branch");
|
|
|
|
get_both([0x24, 0x2C], "BIT");
|
|
|
|
get_both([0x00], "BRK");
|
|
|
|
get_both([0x40], "RTI");
|
|
|
|
get_both([0x60], "RTS");
|
|
|
|
get_test([0x6C], "JMP_ind", "isCMOS!T");
|
|
|
|
get_expect([0x4C, 0x6C], "JMP");
|
|
|
|
get_both([0x20], "JSR");
|
|
|
|
get_both([0x88], "DEC_reg", "Reg.Y");
|
|
|
|
get_both([0xCA], "DEC_reg", "Reg.X");
|
|
|
|
get_both([0xC8], "INC_reg", "Reg.Y");
|
|
|
|
get_both([0xE8], "INC_reg", "Reg.X");
|
|
|
|
get_both([0xE6, 0xEE, 0xF6, 0xFE], "INC");
|
|
|
|
get_both([0xC6, 0xCE, 0xD6, 0xDE], "DEC");
|
|
|
|
get_both([0x2A], "ROL", "true");
|
|
|
|
get_both([0x0A], "ASL", "true");
|
|
|
|
get_both([0x6A], "ROR", "true");
|
|
|
|
get_both([0x4A], "LSR", "true");
|
|
|
|
get_both([0x26, 0x2E, 0x36, 0x3E], "ROL", "false");
|
|
|
|
get_both([0x06, 0x0E, 0x16, 0x1E], "ASL", "false");
|
|
|
|
get_both([0x66, 0x6E, 0x76, 0x7E], "ROR", "false");
|
|
|
|
get_both([0x46, 0x4E, 0x56, 0x5E], "LSR", "false");
|
|
|
|
get_both([0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D],
|
|
|
|
"ADC", "isCMOS!T");
|
|
|
|
get_both([0xE1, 0xE5, 0xE9, 0xED, 0xF1, 0xF5, 0xF9, 0xFD],
|
|
|
|
"SBC", "isCMOS!T");
|
|
|
|
get_both([0xC1, 0xC5, 0xC9, 0xCD, 0xD1, 0xD5, 0xD9, 0xDD], "cmp", "Reg.A");
|
|
|
|
get_both([0x01, 0x05, 0x09, 0x0D, 0x11, 0x15, 0x19, 0x1D], "ORA");
|
|
|
|
get_both([0x21, 0x25, 0x29, 0x2D, 0x31, 0x35, 0x39, 0x3D], "AND");
|
|
|
|
get_both([0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D], "EOR");
|
|
|
|
get_both([0xE0, 0xE4, 0xEC], "cmp", "Reg.X");
|
|
|
|
get_both([0xC0, 0xC4, 0xCC], "cmp", "Reg.Y");
|
2012-03-31 19:44:23 +00:00
|
|
|
|
2012-04-03 00:58:51 +00:00
|
|
|
static if (isNMOS!T)
|
|
|
|
{
|
|
|
|
get_addr(HLT_OPS!T, "implied");
|
|
|
|
|
|
|
|
get_test([0xA3, 0xA7, 0xAF, 0xB3, 0xB7, 0xBF], "load", "Reg.A");
|
|
|
|
get_expect([0xA3, 0xA7, 0xAF, 0xB3, 0xB7, 0xBF], "LAX");
|
|
|
|
get_expect([0x1A, 0x3A, 0x5A, 0x7A, 0xDA, 0xEA, 0xFA], "NOP");
|
|
|
|
get_expect([0x0C, 0x1C, 0x3C, 0x5C, 0x7C, 0xDC, 0xFC], "NOP");
|
|
|
|
get_expect([0x80, 0x82, 0x89, 0xC2, 0xE2], "NOP");
|
|
|
|
get_expect([0x04, 0x44, 0x64], "NOP");
|
|
|
|
get_expect([0x14, 0x34, 0x54, 0x74, 0xD4, 0xF4], "NOP");
|
|
|
|
get_both([0x83, 0x87, 0x8F, 0x97], "SAX");
|
2012-04-12 03:32:16 +00:00
|
|
|
get_both([0x03, 0x07, 0x0F, 0x13, 0x17, 0x1B, 0x1F], "SLO");
|
2012-04-03 00:58:51 +00:00
|
|
|
get_both([0x23, 0x27, 0x2F, 0x33, 0x37, 0x3B, 0x3F], "RLA");
|
2012-04-12 03:32:16 +00:00
|
|
|
get_both([0x43, 0x47, 0x4F, 0x53, 0x57, 0x5B, 0x5F], "SRE");
|
2012-04-03 00:58:51 +00:00
|
|
|
get_both([0x63, 0x67, 0x6F, 0x73, 0x77, 0x7B, 0x7F], "RRA");
|
2012-04-12 03:32:16 +00:00
|
|
|
get_both([0xE3, 0xE7, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF], "ISC");
|
|
|
|
get_both([0xC3, 0xC7, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF], "DCP");
|
2012-04-03 00:58:51 +00:00
|
|
|
get_both([0xEB], "SBC", "false");
|
|
|
|
|
|
|
|
// TODO: implement these opcode tests
|
|
|
|
get_test([0x0B, 0x2B, 0x4B, 0x6B, 0x8B, 0x93, 0x9B, 0x9C, 0x9E,
|
|
|
|
0x9F, 0xAB, 0xBB, 0xCB, 0xEB], "abort");
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-04-03 00:58:51 +00:00
|
|
|
get_addr(NOP1_OPS!T, "implied");
|
|
|
|
get_addr(ZPI_OPS!T, "zpi");
|
|
|
|
get_both([0x12], "ORA");
|
|
|
|
get_both([0x32], "AND");
|
|
|
|
get_both([0x52], "EOR");
|
|
|
|
get_both([0x72], "ADC", "true");
|
|
|
|
get_both([0x92], "store", "Reg.A");
|
|
|
|
get_both([0xB2], "load", "Reg.A");
|
|
|
|
get_both([0xD2], "cmp", "Reg.A");
|
|
|
|
get_both([0xF2], "SBC", "true");
|
|
|
|
get_both([0x5A], "push", "Reg.Y");
|
|
|
|
get_both([0xDA], "push", "Reg.X");
|
|
|
|
get_both([0x7A], "pull", "Reg.Y");
|
|
|
|
get_both([0xFA], "pull", "Reg.X");
|
|
|
|
get_expect([0xEA], "NOP");
|
|
|
|
get_expect(NOP1_OPS!T, "NOP");
|
|
|
|
get_expect([0x02, 0x22, 0x42, 0x62, 0x82, 0xC2, 0xE2], "NOP");
|
|
|
|
get_expect([0x44, 0x54, 0xD4, 0xF4, 0xDC, 0xFC], "NOP");
|
|
|
|
get_both([0x64, 0x74, 0x9C, 0x9E], "STZ");
|
|
|
|
get_both([0x34, 0x3C], "BIT");
|
|
|
|
get_test([0x89], "BIT"); get_expect([0x89], "BIT_imm");
|
|
|
|
get_both([0x14, 0x1C], "TRB");
|
|
|
|
get_both([0x04, 0x0C], "TSB");
|
|
|
|
get_test([0x7C], "JMP_inx");
|
|
|
|
get_expect([0x7C], "JMP");
|
|
|
|
get_both([0xB2], "load", "Reg.A");
|
|
|
|
get_both([0x92], "store", "Reg.A");
|
|
|
|
get_test([0x5C], "5C");
|
|
|
|
get_expect([0x5C], "NOP");
|
|
|
|
get_both([0x3A], "DEC_reg", "Reg.A");
|
|
|
|
get_both([0x1A], "INC_reg", "Reg.A");
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto ret = "final switch (opcode)\n{\n";
|
|
|
|
for (auto i = 0; i < 256; i++)
|
|
|
|
{
|
|
|
|
ret ~= " case 0x" ~ to!string(i, 16) ~ ":\n" ~
|
2012-04-03 00:58:51 +00:00
|
|
|
" " ~ tmp1[i] ~ " " ~ tmp2[i] ~
|
|
|
|
" " ~ tmp3[i] ~ " break;\n";
|
2012-03-31 19:44:23 +00:00
|
|
|
}
|
|
|
|
return ret ~ "\n}";
|
|
|
|
}
|
2012-04-09 01:06:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
alias void delegate(ubyte, ref Expected, CpuInfo, const ref TestMemory, string)
|
|
|
|
testreport;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Runs one opcode. Calls expect for the expected values of the cpu
|
|
|
|
* registers and memory. Calls report with the expected and actual
|
|
|
|
* values.
|
|
|
|
*/
|
|
|
|
auto run_opcode_test(T)(testexpect expect, testreport report)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
auto testcpu = makeCpu!T(cpu);
|
|
|
|
auto mem = TestMemory(data);
|
|
|
|
auto expected = Expected(cpu, mem);
|
|
|
|
expect(expected, info);
|
|
|
|
connectMem(testcpu, mem);
|
|
|
|
runOneOpcode(testcpu);
|
|
|
|
auto cpuResult = CpuInfo.fromCpu(testcpu);
|
|
|
|
report(opcode, expected, cpuResult, mem, T.stringof ~ " | " ~ msg);
|
|
|
|
callNext();
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Dummy function. Reports nothing.
|
|
|
|
auto report_none()
|
|
|
|
{
|
|
|
|
void report(ubyte opcode, ref Expected expected, CpuInfo cpu,
|
|
|
|
const ref TestMemory mem, string msg)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
return &report;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Prints the differences between expected and actual cpu/memory.
|
|
|
|
auto report_debug()
|
|
|
|
{
|
|
|
|
void report(ubyte opcode, ref Expected expected, CpuInfo cpu,
|
|
|
|
const ref TestMemory mem, string msg)
|
|
|
|
{
|
|
|
|
import std.stdio;
|
|
|
|
bool badCpu = (expected.cpu != cpu);
|
|
|
|
bool badMem = (expected.mem != mem);
|
|
|
|
if (badCpu || badMem)
|
|
|
|
writeln(format("[%0.2X] %s", opcode, msg));
|
|
|
|
if (badCpu)
|
|
|
|
{
|
|
|
|
writeln(" expect ", expected.cpu);
|
|
|
|
writeln(" actual ", cpu);
|
|
|
|
}
|
|
|
|
if (badMem)
|
|
|
|
{
|
|
|
|
foreach (h; MemDiff(expected.mem, mem))
|
|
|
|
{
|
|
|
|
writeln(format(" %0.4X | %s", h.base, formatMemory(h.a, 8)));
|
|
|
|
writeln(format(" | %s", formatMemory(h.b, 8)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (badCpu || badMem) throw new Exception("BAD");
|
|
|
|
}
|
|
|
|
return &report;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void test_one_opcode(T)(ubyte opcode, testreport report)
|
|
|
|
{
|
|
|
|
TestSetup setup_addr;
|
|
|
|
TestSetup setup_test;
|
|
|
|
testexpect expect;
|
|
|
|
|
|
|
|
mixin(getMemSetup!T());
|
|
|
|
|
|
|
|
auto setup = connect(setup_mask_flags(), setup_addr, setup_test);
|
|
|
|
auto run = connect(setup, run_opcode_test!T(expect, report));
|
|
|
|
run.run(opcode);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
T[] If(alias cond, T)(T[] actions)
|
|
|
|
{
|
|
|
|
if (cond)
|
|
|
|
return actions;
|
|
|
|
else
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for register opcodes.
|
|
|
|
auto accesses_reg(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(REG_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
cycles = 2;
|
|
|
|
return [Bus(Action.READ, pc)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+1)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for push opcodes.
|
|
|
|
auto accesses_push(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(PUSH_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
|
|
|
|
cycles = 3;
|
|
|
|
return [Bus(Action.READ, pc)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+1)]) ~
|
|
|
|
[Bus(Action.WRITE, sp)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for pull opcodes.
|
|
|
|
auto accesses_pull(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(PULL_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, 1);
|
|
|
|
|
|
|
|
cycles = 4;
|
|
|
|
return [Bus(Action.READ, pc)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, sp)]) ~
|
|
|
|
[Bus(Action.READ, sp1)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for immediate mode opcodes.
|
|
|
|
auto accesses_imm(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(IMM_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
bool decimal = isCMOS!T && getFlag(cpu, Flag.D) &&
|
|
|
|
BCD_OPS!T.canFind(opcode);
|
|
|
|
|
|
|
|
cycles = 2 + decimal;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
If!decimal(If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+2)]));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for branch opcodes.
|
|
|
|
auto accesses_rel(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
|
|
assert(BRANCH_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto base = cast(ushort)(pc + 2);
|
|
|
|
bool branch = wouldBranch(cpu, opcode);
|
|
|
|
ushort wrongPage = pageWrapAdd(base, cast(byte)op1);
|
|
|
|
bool px = wrongPage != pageCrossAdd(base, cast(byte)op1);
|
|
|
|
ushort wrongAddr = isNMOS!T ? wrongPage : base;
|
|
|
|
|
|
|
|
cycles = 2 + branch + px;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
If!branch(If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+2)] ~
|
|
|
|
If!px(
|
|
|
|
[Bus(Action.READ, wrongAddr)])));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for zeropage mode opcodes.
|
|
|
|
auto accesses_zpg(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
|
|
assert(ZPG_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
cycles = 2; // + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
accesses_end(cpu, opcode, 2, op1, cycles);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for absolute mode opcodes.
|
|
|
|
auto accesses_abs(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1], op2 = mem[pc+2];
|
|
|
|
assert(ABS_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto addr = address(op1, op2);
|
|
|
|
|
|
|
|
cycles = 3; // + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, pc+2)] ~
|
|
|
|
accesses_end(cpu, opcode, 3, addr, cycles);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for zeropage,x/y mode opcodes.
|
|
|
|
auto accesses_zpxy(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
|
|
bool useX = ZPX_OPS!T.canFind(opcode);
|
|
|
|
assert(useX || ZPY_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto idx = (useX ? getX(cpu) : getY(cpu));
|
|
|
|
auto addr = pageWrapAdd(op1, idx);
|
|
|
|
|
|
|
|
cycles = 3; // + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
If!(isNMOS!T)(
|
|
|
|
[Bus(Action.READ, op1)]) ~
|
|
|
|
If!(isCMOS!T)(
|
|
|
|
[Bus(Action.READ, pc+2)])) ~ // XXX
|
|
|
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* According to "Understanding the Apple IIe", the extra read on
|
|
|
|
* the 65C02 (marked XXX above) is the address of the last operand
|
|
|
|
* byte (pc + 1).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for absolute,x/y mode opcodes.
|
|
|
|
auto accesses_abxy(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
auto op1 = mem[pc+1], op2 = mem[pc+2];
|
|
|
|
bool useX = ABX_OPS!T.canFind(opcode);
|
|
|
|
assert(useX || ABY_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
|
|
|
|
auto idx = useX ? getX(cpu) : getY(cpu);
|
|
|
|
auto base = address(op1, op2);
|
|
|
|
auto guess = pageWrapAdd(base, idx);
|
|
|
|
auto addr = pageCrossAdd(base, idx);
|
|
|
|
|
|
|
|
cycles = 3; // + accesses_px + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, pc+2)] ~
|
|
|
|
accesses_px(cpu, opcode, 3, guess, addr, cycles) ~
|
|
|
|
accesses_end(cpu, opcode, 3, addr, cycles);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for indirect zeropage,x mode opcodes.
|
|
|
|
auto accesses_izx(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
|
|
assert(IZX_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto idx = getX(cpu);
|
|
|
|
auto ial = pageWrapAdd(op1, idx);
|
|
|
|
auto iah = pageWrapAdd(ial, 1);
|
|
|
|
auto addr = address(mem[ial], mem[iah]);
|
|
|
|
|
|
|
|
cycles = 5; // + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
If!(isNMOS!T)(
|
|
|
|
[Bus(Action.READ, op1)]) ~
|
|
|
|
If!(isCMOS!T)(
|
|
|
|
[Bus(Action.READ, pc+2)])) ~ // XXX
|
|
|
|
[Bus(Action.READ, ial),
|
|
|
|
Bus(Action.READ, iah)] ~
|
|
|
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* According to "Understanding the Apple IIe", the extra read on
|
|
|
|
* the 65C02 (marked XXX above) is the address of the last operand
|
|
|
|
* byte (pc + 1).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for indirect zeropage,y mode opcodes.
|
|
|
|
auto accesses_izy(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
|
|
assert(IZY_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto idx = getY(cpu);
|
|
|
|
auto ial = op1;
|
|
|
|
auto iah = pageWrapAdd(ial, 1);
|
|
|
|
auto base = address(mem[ial], mem[iah]);
|
|
|
|
auto guess = pageWrapAdd(base, idx);
|
|
|
|
auto addr = pageCrossAdd(base, idx);
|
|
|
|
|
|
|
|
cycles = 4; // + accesses_px + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, ial),
|
|
|
|
Bus(Action.READ, iah)] ~
|
|
|
|
accesses_px(cpu, opcode, 2, guess, addr, cycles) ~
|
|
|
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for indirect zeropage mode opcodes.
|
|
|
|
auto accesses_zpi(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T && isCMOS!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1];
|
|
|
|
assert(ZPI_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
auto ial = op1;
|
|
|
|
auto iah = pageWrapAdd(ial, 1);
|
|
|
|
auto addr = address(mem[ial], mem[iah]);
|
|
|
|
|
|
|
|
cycles = 4; // + accesses_end
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, ial),
|
|
|
|
Bus(Action.READ, iah)] ~
|
|
|
|
accesses_end(cpu, opcode, 2, addr, cycles);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for NMOS HLT opcodes.
|
|
|
|
auto accesses_hlt(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T && isNMOS!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(HLT_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
cycles = 1;
|
|
|
|
return [Bus(Action.READ, pc)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for 1-cycle NOPs.
|
|
|
|
auto accesses_nop1(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T && isCMOS!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(NOP1_OPS!T.canFind(opcode));
|
|
|
|
|
|
|
|
cycles = 1;
|
|
|
|
return [Bus(Action.READ, pc)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto accesses_px(T)(T cpu, ubyte opcode, int opLen, ushort guess, ushort right,
|
|
|
|
ref int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
bool noShortcut = WRITE_OPS!T.canFind(opcode) ||
|
|
|
|
(isNMOS!T ? (RMW_OPS!T.canFind(opcode))
|
|
|
|
: (opcode == 0xDE || opcode == 0xFE));
|
|
|
|
|
|
|
|
if (guess != right)
|
|
|
|
{
|
|
|
|
cycles += 1;
|
|
|
|
return If!(isStrict!T)(
|
|
|
|
If!(isNMOS!T)([Bus(Action.READ, guess)]) ~
|
|
|
|
If!(isCMOS!T)([Bus(Action.READ, pc + opLen)])); // XXX
|
|
|
|
}
|
|
|
|
else if (noShortcut)
|
|
|
|
{
|
|
|
|
cycles += 1;
|
|
|
|
return If!(isStrict!T)([Bus(Action.READ, guess)]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return cast(Bus[])[];
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* According to "Understanding the Apple IIe", the extra read on
|
|
|
|
* the 65C02 (marked XXX above) is the address of the last operand
|
|
|
|
* byte (pc + opLen - 1) for abx/aby, or the address of the high
|
|
|
|
* byte of the indirect address (op1 + 1) for izy.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
auto accesses_end(T)(T cpu, ubyte opcode, int opLen, ushort addr,
|
|
|
|
ref int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
bool rmw = RMW_OPS!T.canFind(opcode);
|
|
|
|
bool write = !rmw && WRITE_OPS!T.canFind(opcode);
|
|
|
|
bool read = !rmw && !write;
|
|
|
|
bool decimal = isCMOS!T && getFlag(cpu, Flag.D) &&
|
|
|
|
BCD_OPS!T.canFind(opcode);
|
|
|
|
|
|
|
|
cycles += (rmw ? 3 : (write ? 1 : (1 + decimal)));
|
|
|
|
return If!read(
|
|
|
|
[Bus(Action.READ, addr)] ~
|
|
|
|
If!decimal(If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc + opLen)]))) ~
|
|
|
|
If!write(
|
|
|
|
[Bus(Action.WRITE, addr)]) ~
|
|
|
|
If!rmw(
|
|
|
|
[Bus(Action.READ, addr)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
If!(isNMOS!T)(
|
|
|
|
[Bus(Action.WRITE, addr)]) ~
|
|
|
|
If!(isCMOS!T)(
|
|
|
|
[Bus(Action.READ, addr)])) ~
|
|
|
|
[Bus(Action.WRITE, addr)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for RTS.
|
|
|
|
auto accesses_op_RTS(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x60);
|
|
|
|
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, 1);
|
|
|
|
auto sp2 = pageWrapAdd(sp, 2);
|
|
|
|
auto ret = address(mem[sp1], mem[sp2]);
|
|
|
|
|
|
|
|
cycles = 6;
|
|
|
|
return [Bus(Action.READ, pc)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, sp)]) ~
|
|
|
|
[Bus(Action.READ, sp1),
|
|
|
|
Bus(Action.READ, sp2)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, ret)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for RTI.
|
|
|
|
auto accesses_op_RTI(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x40);
|
|
|
|
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, 1);
|
|
|
|
auto sp2 = pageWrapAdd(sp, 2);
|
|
|
|
auto sp3 = pageWrapAdd(sp, 3);
|
|
|
|
|
|
|
|
cycles = 6;
|
|
|
|
return [Bus(Action.READ, pc)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, sp)]) ~
|
|
|
|
[Bus(Action.READ, sp1),
|
|
|
|
Bus(Action.READ, sp2),
|
|
|
|
Bus(Action.READ, sp3)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for BRK.
|
|
|
|
auto accesses_op_BRK(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x00);
|
|
|
|
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, -1);
|
|
|
|
auto sp2 = pageWrapAdd(sp, -2);
|
|
|
|
|
|
|
|
cycles = 7;
|
|
|
|
return [Bus(Action.READ, pc)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+1)]) ~
|
|
|
|
[Bus(Action.WRITE, sp),
|
|
|
|
Bus(Action.WRITE, sp1),
|
|
|
|
Bus(Action.WRITE, sp2),
|
|
|
|
Bus(Action.READ, 0xFFFE),
|
|
|
|
Bus(Action.READ, 0xFFFF)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for JSR
|
|
|
|
auto accesses_op_JSR(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x20);
|
|
|
|
|
|
|
|
auto sp = getSP(cpu);
|
|
|
|
auto sp1 = pageWrapAdd(sp, -1);
|
|
|
|
|
|
|
|
cycles = 6;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, sp)]) ~
|
|
|
|
[Bus(Action.WRITE, sp),
|
|
|
|
Bus(Action.WRITE, sp1),
|
|
|
|
Bus(Action.READ, pc+2)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for JMP absolute
|
|
|
|
auto accesses_op_JMP_abs(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x4C);
|
|
|
|
|
|
|
|
cycles = 3;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, pc+2)];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for JMP indirect
|
|
|
|
auto accesses_op_JMP_ind(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc], op1 = mem[pc+1], op2 = mem[pc+2];
|
|
|
|
assert(opcode == 0x6C);
|
|
|
|
|
|
|
|
auto ial = address(op1, op2);
|
|
|
|
auto iah = (isNMOS!T ? pageWrapAdd(ial, 1)
|
|
|
|
: pageCrossAdd(ial, 1));
|
|
|
|
|
|
|
|
cycles = 5 + isCMOS!T;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, pc+2)] ~
|
|
|
|
If!(isStrict!T)(If!(isCMOS!T)(
|
|
|
|
[Bus(Action.READ, pc+3)])) ~ // XXX
|
|
|
|
[Bus(Action.READ, ial),
|
|
|
|
Bus(Action.READ, iah)];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* According to "Understanding the Apple IIe", the extra read on
|
|
|
|
* the 65C02 (marked XXX above) is the address of the last operand
|
|
|
|
* byte (pc + 2).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for JMP indirect,x
|
|
|
|
auto accesses_op_JMP_inx(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T && isCMOS!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x7C);
|
|
|
|
|
|
|
|
auto idx = getX(cpu);
|
|
|
|
auto base = address(mem[pc+1], mem[pc+2]);
|
|
|
|
auto ial = pageCrossAdd(base, idx);
|
|
|
|
auto iah = pageCrossAdd(ial, 1);
|
|
|
|
|
|
|
|
cycles = 6;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1),
|
|
|
|
Bus(Action.READ, pc+2)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+3)]) ~ // XXX
|
|
|
|
[Bus(Action.READ, ial),
|
|
|
|
Bus(Action.READ, iah)];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* According to "Understanding the Apple IIe", the extra read on
|
|
|
|
* the 65C02 (marked XXX above) is the address of the last operand
|
|
|
|
* byte (pc + 2).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Bus access pattern for CMOS opcode 5C
|
|
|
|
auto accesses_op_5C(T)(T cpu, ref TestMemory mem, out int cycles)
|
|
|
|
if (isCpu!T && isCMOS!T)
|
|
|
|
{
|
|
|
|
auto pc = getPC(cpu);
|
|
|
|
auto opcode = mem[pc];
|
|
|
|
assert(opcode == 0x5C);
|
|
|
|
|
|
|
|
auto weird = address(mem[pc+1], 0xFF);
|
|
|
|
|
|
|
|
cycles = 8;
|
|
|
|
return [Bus(Action.READ, pc),
|
|
|
|
Bus(Action.READ, pc+1)] ~
|
|
|
|
If!(isStrict!T)(
|
|
|
|
[Bus(Action.READ, pc+2),
|
|
|
|
Bus(Action.READ, weird),
|
|
|
|
Bus(Action.READ, 0xFFFF),
|
|
|
|
Bus(Action.READ, 0xFFFF),
|
|
|
|
Bus(Action.READ, 0xFFFF),
|
|
|
|
Bus(Action.READ, 0xFFFF)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Associates opcodes with expected access patterns.
|
|
|
|
string getExpected(T)()
|
|
|
|
{
|
|
|
|
string[] tmp = new string[256];
|
|
|
|
|
|
|
|
void add_op(const(ubyte[]) list, string fname)
|
|
|
|
{
|
|
|
|
foreach(op; list)
|
|
|
|
{
|
|
|
|
tmp[op] = " case 0x" ~ to!string(op, 16) ~ ": " ~
|
|
|
|
"expected = &" ~ fname ~ "!T; break;";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
add_op(REG_OPS!T, "accesses_reg");
|
|
|
|
add_op(PUSH_OPS!T, "accesses_push");
|
|
|
|
add_op(PULL_OPS!T, "accesses_pull");
|
|
|
|
add_op(BRANCH_OPS!T, "accesses_rel");
|
|
|
|
add_op(IMM_OPS!T, "accesses_imm");
|
|
|
|
add_op(ZPG_OPS!T, "accesses_zpg");
|
|
|
|
add_op(ZPX_OPS!T, "accesses_zpxy");
|
|
|
|
add_op(ZPY_OPS!T, "accesses_zpxy");
|
|
|
|
add_op(ABS_OPS!T, "accesses_abs");
|
|
|
|
add_op(ABX_OPS!T, "accesses_abxy");
|
|
|
|
add_op(ABY_OPS!T, "accesses_abxy");
|
|
|
|
add_op(IZX_OPS!T, "accesses_izx");
|
|
|
|
add_op(IZY_OPS!T, "accesses_izy");
|
|
|
|
add_op([0x00], "accesses_op_BRK");
|
|
|
|
add_op([0x20], "accesses_op_JSR");
|
|
|
|
add_op([0x40], "accesses_op_RTI");
|
|
|
|
add_op([0x4C], "accesses_op_JMP_abs");
|
|
|
|
add_op([0x60], "accesses_op_RTS");
|
|
|
|
add_op([0x6C], "accesses_op_JMP_ind");
|
|
|
|
static if (isNMOS!T)
|
|
|
|
add_op(HLT_OPS!T, "accesses_hlt");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
add_op(ZPI_OPS!T, "accesses_zpi");
|
|
|
|
add_op(NOP1_OPS!T, "accesses_nop1");
|
|
|
|
add_op([0x7C], "accesses_op_JMP_inx");
|
|
|
|
add_op([0x5C], "accesses_op_5C");
|
|
|
|
}
|
|
|
|
|
|
|
|
return "final switch (opcode)\n{\n" ~ join(tmp, "\n") ~ "\n}";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template timesetup_t(T)
|
|
|
|
{
|
|
|
|
alias Bus[] function(T, ref TestMemory, out int) timesetup_t;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
alias void delegate(int, const Bus[], int, const Bus[], ubyte, string)
|
|
|
|
busreport;
|
|
|
|
|
|
|
|
|
|
|
|
auto run_timing_test(T)(timesetup_t!T expect, busreport report)
|
|
|
|
{
|
|
|
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
|
|
|
string msg, TestSetup* next)
|
|
|
|
{
|
|
|
|
mixin testCallNext;
|
|
|
|
auto testcpu = makeCpu!T(cpu);
|
|
|
|
auto mem = TestMemory(data);
|
|
|
|
|
|
|
|
int expCycles;
|
|
|
|
auto expBus = expect(testcpu, mem, expCycles);
|
|
|
|
expBus = expBus ~ new Bus[8 - expBus.length];
|
|
|
|
|
|
|
|
connectMem(testcpu, mem);
|
|
|
|
auto actualBus = recordBus(testcpu);
|
|
|
|
auto actualCycles = recordCycles(testcpu);
|
|
|
|
|
|
|
|
runOneOpcode(testcpu);
|
|
|
|
|
|
|
|
report(actualCycles, actualBus, expCycles, expBus,
|
|
|
|
opcode, T.stringof ~ " | " ~ msg);
|
|
|
|
callNext();
|
|
|
|
}
|
|
|
|
return TestSetup(&setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto report_timing_debug()
|
|
|
|
{
|
|
|
|
void report(int actualCycles, const Bus[] actualBus,
|
|
|
|
int expectCycles, const Bus[] expectBus,
|
|
|
|
ubyte opcode, string msg)
|
|
|
|
{
|
|
|
|
if (actualBus != expectBus)
|
|
|
|
{
|
|
|
|
// XXX make error message, throw
|
|
|
|
}
|
|
|
|
if (actualCycles != expectCycles)
|
|
|
|
{
|
|
|
|
// XXX make error message, throw
|
|
|
|
}
|
|
|
|
if (actualBus == expectBus && actualCycles == expectCycles) {}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
write(format("[%0.2X] %s", opcode, msg));
|
|
|
|
writeln();
|
|
|
|
writeln(expectCycles, " ", actualCycles);
|
|
|
|
writeln(expectBus);
|
|
|
|
writeln(actualBus);
|
|
|
|
throw new TestException("timing");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &report;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Tests the bus access patterns and cycles taken for a given opcode.
|
|
|
|
void test_opcode_timing(T)(ubyte opcode, busreport report)
|
|
|
|
{
|
|
|
|
TestSetup setup_addr;
|
|
|
|
TestSetup setup_test;
|
|
|
|
testexpect expect;
|
|
|
|
timesetup_t!T expected;
|
|
|
|
|
|
|
|
mixin(getMemSetup!T());
|
|
|
|
mixin(getExpected!T());
|
|
|
|
|
|
|
|
auto setup = connect(setup_mask_flags(), setup_addr, setup_test);
|
|
|
|
auto run = connect(setup, run_timing_test!T(expected, report));
|
|
|
|
run.run(opcode);
|
|
|
|
}
|
2012-04-12 22:42:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
version(Strict)
|
|
|
|
enum testStrict = true;
|
|
|
|
else
|
|
|
|
enum testStrict = false;
|
|
|
|
version(Cumulative)
|
|
|
|
enum testCumulative = true;
|
|
|
|
else
|
|
|
|
enum testCumulative = false;
|
|
|
|
|
|
|
|
|
|
|
|
struct CheckOptions
|
|
|
|
{
|
|
|
|
enum Addr
|
|
|
|
{
|
|
|
|
IMP, IMM, ZP, ZPX, ZPY, IZX, IZY, ABS, ABX, ABY, IND, REL,
|
|
|
|
ZPI, ABI, NP1, NP8, KIL
|
|
|
|
}
|
|
|
|
|
|
|
|
string[] opcodes;
|
|
|
|
ubyte[] codes6502;
|
|
|
|
ubyte[] codes65C02;
|
|
|
|
Addr[] addr;
|
|
|
|
|
|
|
|
this(string[] args)
|
|
|
|
{
|
|
|
|
getopt(args, "op", &opcodes, "addr", &addr);
|
|
|
|
foreach (op; opcodes)
|
|
|
|
{
|
|
|
|
if (op.startsWith("0x") || op.startsWith("0X"))
|
|
|
|
op = op[2..$];
|
|
|
|
if (isNumeric(op))
|
|
|
|
{
|
|
|
|
int code = to!int(op, 16);
|
|
|
|
if (code >= 0x00 && code <= 0xFF)
|
|
|
|
{
|
|
|
|
codes6502 ~= [cast(ubyte)code];
|
|
|
|
codes65C02 ~= [cast(ubyte)code];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
foreach (code, name; OP_NAMES_6502)
|
|
|
|
if (name == op) codes6502 ~= [cast(ubyte)code];
|
|
|
|
foreach (code, name; OP_NAMES_65C02)
|
|
|
|
if (name == op) codes65C02 ~= [cast(ubyte)code];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach (a; addr)
|
|
|
|
{
|
|
|
|
foreach (code, mode; ADDR_MODES_6502)
|
|
|
|
if (a == mode) codes6502 ~= [cast(ubyte)code];
|
|
|
|
foreach (code, mode; ADDR_MODES_65C02)
|
|
|
|
if (a == mode) codes65C02 ~= [cast(ubyte)code];
|
|
|
|
}
|
|
|
|
if (opcodes.empty && addr.empty)
|
|
|
|
{
|
|
|
|
codes6502 = codes65C02 = new ubyte[256];
|
|
|
|
foreach (code; 0..256)
|
|
|
|
{
|
|
|
|
codes6502[code] = cast(ubyte)code;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|