mirror of
https://github.com/edmccard/twoapple-reboot.git
synced 2024-12-30 04:32:44 +00:00
Test refactoring; complete timing and nearly complete functional
This commit is contained in:
parent
afc38b6775
commit
f79021fc14
2927
test/base.d
2927
test/base.d
File diff suppressed because it is too large
Load Diff
243
test/cpu.d
243
test/cpu.d
@ -58,6 +58,19 @@ template CPU(string arch, bool strict, bool cumulative)
|
||||
}
|
||||
|
||||
|
||||
auto makeCpu(T)(CpuInfo info)
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto cpu = new T();
|
||||
cpu.programCounter = info.PC;
|
||||
cpu.stackPointer = info.SP;
|
||||
cpu.flag.fromByte(info.S);
|
||||
cpu.accumulator = info.A;
|
||||
cpu.xIndex = info.X;
|
||||
cpu.yIndex = info.Y;
|
||||
return cpu;
|
||||
}
|
||||
|
||||
// Connects test memory to a cpu.
|
||||
void connectMem(T, S)(T cpu, ref S mem)
|
||||
if (isCpu!T)
|
||||
@ -222,6 +235,33 @@ if (isCpu!T)
|
||||
}
|
||||
|
||||
|
||||
struct CpuInfo
|
||||
{
|
||||
ushort PC;
|
||||
ubyte SP = 0xFF;
|
||||
ubyte A, X, Y;
|
||||
ubyte S = 0x30;
|
||||
|
||||
string toString() const
|
||||
{
|
||||
return format("PC %0.4X SP %0.2X S %0.2X A %0.2X X %0.2X Y %0.2X",
|
||||
PC, SP, S, A, X, Y);
|
||||
}
|
||||
|
||||
static CpuInfo fromCpu(T)(T cpu)
|
||||
{
|
||||
CpuInfo info;
|
||||
info.PC = cpu.programCounter;
|
||||
info.SP = cpu.stackPointer;
|
||||
info.A = cpu.accumulator;
|
||||
info.X = cpu.xIndex;
|
||||
info.Y = cpu.yIndex;
|
||||
info.S = cpu.flag.toByte();
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sets the program counter.
|
||||
void setPC(T)(T cpu, int addr)
|
||||
if (isCpu!T)
|
||||
@ -229,6 +269,16 @@ if (isCpu!T)
|
||||
cpu.programCounter = cast(ushort)addr;
|
||||
}
|
||||
|
||||
void setPC(T : CpuInfo)(ref T cpu, int addr)
|
||||
{
|
||||
cpu.PC = cast(ushort)addr;
|
||||
}
|
||||
|
||||
void incPC(T : CpuInfo)(ref T cpu, int amt = 1)
|
||||
{
|
||||
cpu.PC = pageCrossAdd(cpu.PC, amt);
|
||||
}
|
||||
|
||||
// Returns the program counter.
|
||||
ushort getPC(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
@ -236,6 +286,11 @@ if (isCpu!T)
|
||||
return cpu.programCounter;
|
||||
}
|
||||
|
||||
ushort getPC(T : CpuInfo)(ref T cpu)
|
||||
{
|
||||
return cpu.PC;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Sets the stack pointer.
|
||||
@ -250,6 +305,23 @@ if (isCpu!T)
|
||||
cpu.stackPointer = cast(ubyte)val;
|
||||
}
|
||||
|
||||
void setSP(T : CpuInfo)(ref T cpu, int val)
|
||||
{
|
||||
assert(val < 0x0200);
|
||||
cpu.SP = cast(ubyte)val;
|
||||
}
|
||||
|
||||
void incSP(T : CpuInfo)(ref T cpu, int amt = 1)
|
||||
{
|
||||
cpu.SP = cast(ubyte)pageWrapAdd(cpu.SP, amt);
|
||||
}
|
||||
|
||||
void decSP(T : CpuInfo)(ref T cpu, int amt = -1)
|
||||
{
|
||||
cpu.SP = cast(ubyte)pageWrapAdd(cpu.SP, amt);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns the stack address (in the range 0x0100-0x01FF) represented
|
||||
* by the stack pointer.
|
||||
@ -260,6 +332,66 @@ if (isCpu!T)
|
||||
return 0x100 | cpu.stackPointer;
|
||||
}
|
||||
|
||||
ushort getSP(T : CpuInfo)(ref T cpu)
|
||||
{
|
||||
return 0x0100 | cpu.SP;
|
||||
}
|
||||
|
||||
|
||||
// The names of the registers.
|
||||
enum Reg
|
||||
{
|
||||
A, X, Y
|
||||
}
|
||||
|
||||
// Sets a register.
|
||||
void setReg(T)(ref T cpu, Reg reg, int val)
|
||||
if (isCpu!T || is(T == CpuInfo))
|
||||
{
|
||||
final switch (reg)
|
||||
{
|
||||
case Reg.A: setA(cpu, val); break;
|
||||
case Reg.X: setX(cpu, val); break;
|
||||
case Reg.Y: setY(cpu, val); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a register
|
||||
ubyte getReg(T)(ref T cpu, Reg reg)
|
||||
if (isCpu!T || is(T == CpuInfo))
|
||||
{
|
||||
final switch (reg)
|
||||
{
|
||||
case Reg.A: return getA(cpu);
|
||||
case Reg.X: return getX(cpu);
|
||||
case Reg.Y: return getY(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the A register.
|
||||
void setA(T)(T cpu, int val)
|
||||
if (isCpu!T)
|
||||
{
|
||||
cpu.accumulator = cast(ubyte)val;
|
||||
}
|
||||
|
||||
void setA(T : CpuInfo)(ref T cpu, int val)
|
||||
{
|
||||
cpu.A = cast(ubyte)val;
|
||||
}
|
||||
|
||||
// Returns the A register.
|
||||
ubyte getA(T)(T cpu, int val)
|
||||
if (isCpu!T)
|
||||
{
|
||||
return cpu.accumulator;
|
||||
}
|
||||
|
||||
ubyte getA(T : CpuInfo)(ref T cpu)
|
||||
{
|
||||
return cpu.A;
|
||||
}
|
||||
|
||||
|
||||
// Sets the X register.
|
||||
void setX(T)(T cpu, int val)
|
||||
@ -268,6 +400,11 @@ if (isCpu!T)
|
||||
cpu.xIndex = cast(ubyte)val;
|
||||
}
|
||||
|
||||
void setX(T : CpuInfo)(ref T cpu, int val)
|
||||
{
|
||||
cpu.X = cast(ubyte)val;
|
||||
}
|
||||
|
||||
// Returns the X register.
|
||||
ubyte getX(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
@ -275,6 +412,11 @@ if (isCpu!T)
|
||||
return cpu.xIndex;
|
||||
}
|
||||
|
||||
ubyte getX(T : CpuInfo)(ref T cpu)
|
||||
{
|
||||
return cpu.X;
|
||||
}
|
||||
|
||||
|
||||
// Sets the Y register.
|
||||
void setY(T)(T cpu, int val)
|
||||
@ -283,6 +425,11 @@ if (isCpu!T)
|
||||
cpu.yIndex = cast(ubyte)val;
|
||||
}
|
||||
|
||||
void setY(T : CpuInfo)(ref T cpu, int val)
|
||||
{
|
||||
cpu.Y = cast(ubyte)val;
|
||||
}
|
||||
|
||||
// Returns the Y register.
|
||||
ubyte getY(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
@ -290,6 +437,11 @@ if (isCpu!T)
|
||||
return cpu.yIndex;
|
||||
}
|
||||
|
||||
ubyte getY(T : CpuInfo)(ref T cpu)
|
||||
{
|
||||
return cpu.Y;
|
||||
}
|
||||
|
||||
|
||||
// The names of the status flags.
|
||||
enum Flag : ubyte
|
||||
@ -302,6 +454,20 @@ enum Flag : ubyte
|
||||
N = 0x80
|
||||
}
|
||||
|
||||
string flagToString(Flag f)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case Flag.C: return "C";
|
||||
case Flag.Z: return "Z";
|
||||
case Flag.I: return "I";
|
||||
case Flag.D: return "D";
|
||||
case Flag.V: return "V";
|
||||
case Flag.N: return "N";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
// Sets one or more status flags.
|
||||
void setFlag(T)(T cpu, Flag[] flags...)
|
||||
if (isCpu!T)
|
||||
@ -311,6 +477,11 @@ if (isCpu!T)
|
||||
cpu.flag.fromByte(reg);
|
||||
}
|
||||
|
||||
void setFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
||||
{
|
||||
foreach (flag; flags) cpu.S |= flag;
|
||||
}
|
||||
|
||||
// Clears one or more status flags.
|
||||
void clearFlag(T)(T cpu, Flag[] flags...)
|
||||
if (isCpu!T)
|
||||
@ -320,6 +491,11 @@ if (isCpu!T)
|
||||
cpu.flag.fromByte(reg);
|
||||
}
|
||||
|
||||
void clearFlag(T : CpuInfo)(ref T cpu, Flag[] flags...)
|
||||
{
|
||||
foreach (flag; flags) cpu.S &= ~flag;
|
||||
}
|
||||
|
||||
// Returns a status flag.
|
||||
bool getFlag(T)(T cpu, Flag f)
|
||||
if (isCpu!T)
|
||||
@ -327,9 +503,39 @@ if (isCpu!T)
|
||||
return (cpu.flag.toByte() & f) != 0;
|
||||
}
|
||||
|
||||
// Sets or clears a single status flag.
|
||||
void updateFlag(T)(T cpu, Flag f, bool val)
|
||||
bool getFlag(T : CpuInfo)(ref T cpu, Flag f)
|
||||
{
|
||||
return (cpu.S & f) != 0;
|
||||
}
|
||||
|
||||
|
||||
// Sets the status register from a byte.
|
||||
void setStatus(T)(T cpu, int val)
|
||||
if (isCpu!T)
|
||||
{
|
||||
cpu.flag.fromByte(cast(ubyte)val);
|
||||
}
|
||||
|
||||
void setStatus(T : CpuInfo)(ref T cpu, int val)
|
||||
{
|
||||
cpu.S = cast(ubyte)val | 0x30;
|
||||
}
|
||||
|
||||
// Returns the status register as a byte.
|
||||
ubyte getStatus(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
{
|
||||
return cpu.flag.toByte();
|
||||
}
|
||||
|
||||
ubyte getStatus(T : CpuInfo)(ref T cpu)
|
||||
{
|
||||
return cpu.S | 0x30;
|
||||
}
|
||||
|
||||
// Sets or clears a single status flag.
|
||||
void updateFlag(T)(ref T cpu, Flag f, bool val)
|
||||
if (isCpu!T || is(T == CpuInfo))
|
||||
{
|
||||
if (val)
|
||||
setFlag(cpu, f);
|
||||
@ -337,10 +543,15 @@ if (isCpu!T)
|
||||
clearFlag(cpu, f);
|
||||
}
|
||||
|
||||
void setNZ(T : CpuInfo)(ref T cpu, ubyte val)
|
||||
{
|
||||
updateFlag(cpu, Flag.Z, (val == 0));
|
||||
updateFlag(cpu, Flag.N, (val >= 0x80));
|
||||
}
|
||||
|
||||
// Sets or clears the flag required for a given opcode to branch.
|
||||
void expectBranch(T)(T cpu, ubyte opcode)
|
||||
if (isCpu!T)
|
||||
void expectBranch(T)(ref T cpu, ubyte opcode)
|
||||
if (isCpu!T || is(T == CpuInfo))
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
@ -348,19 +559,19 @@ if (isCpu!T)
|
||||
case /*BMI*/ 0x30: setFlag(cpu, Flag.N); break;
|
||||
case /*BVC*/ 0x50: clearFlag(cpu, Flag.V); break;
|
||||
case /*BVS*/ 0x70: setFlag(cpu, Flag.V); break;
|
||||
case /*BRA*/ 0x80: break;
|
||||
case /*BCC*/ 0x90: clearFlag(cpu, Flag.C); break;
|
||||
case /*BCS*/ 0xB0: setFlag(cpu, Flag.C); break;
|
||||
case /*BNE*/ 0xD0: clearFlag(cpu, Flag.Z); break;
|
||||
case /*BEQ*/ 0xF0: setFlag(cpu, Flag.Z); break;
|
||||
default:
|
||||
if (isCMOS!T) { if (opcode == /*BRA*/ 0x80) break; }
|
||||
enforce(0, format("not a branching opcpde %0.2X", opcode));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether an opcode would branch if executed.
|
||||
bool wouldBranch(T)(T cpu, ubyte opcode)
|
||||
if (isCpu!T)
|
||||
bool wouldBranch(T)(ref T cpu, ubyte opcode)
|
||||
if (isCpu!T || is(T == CpuInfo))
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
@ -368,18 +579,19 @@ if (isCpu!T)
|
||||
case /*BMI*/ 0x30: return getFlag(cpu, Flag.N);
|
||||
case /*BVC*/ 0x50: return !getFlag(cpu, Flag.V);
|
||||
case /*BVS*/ 0x70: return getFlag(cpu, Flag.V);
|
||||
case /*BRA*/ 0x80: return true;
|
||||
case /*BCC*/ 0x90: return !getFlag(cpu, Flag.C);
|
||||
case /*BCS*/ 0xB0: return getFlag(cpu, Flag.C);
|
||||
case /*BNE*/ 0xD0: return !getFlag(cpu, Flag.Z);
|
||||
case /*BEQ*/ 0xF0: return getFlag(cpu, Flag.Z);
|
||||
default:
|
||||
if (isCMOS!T) { if (opcode == /*BRA*/ 0x80) return true; }
|
||||
assert(0, format("not a branching opcpde %0.2X", opcode));
|
||||
}
|
||||
}
|
||||
|
||||
// Sets or clears the flag required for a given opcode to not branch.
|
||||
void expectNoBranch(T)(T cpu, ubyte opcode)
|
||||
void expectNoBranch(T)(ref T cpu, ubyte opcode)
|
||||
if (isCpu!T || is(T == CpuInfo))
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
@ -406,6 +618,17 @@ ushort address(ubyte l, ubyte h)
|
||||
}
|
||||
|
||||
|
||||
ubyte addrLo(ushort addr)
|
||||
{
|
||||
return cast(ubyte)(addr & 0xFF);
|
||||
}
|
||||
|
||||
ubyte addrHi(ushort addr)
|
||||
{
|
||||
return cast(ubyte)(addr >> 8);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Adds an offset to an address, resulting in an address in the same
|
||||
* page.
|
||||
@ -435,7 +658,7 @@ ubyte XX()
|
||||
// A number different from some other number.
|
||||
ubyte notXX(ubyte val)
|
||||
{
|
||||
return cast(ubyte)(val ^ 0xAA);
|
||||
return cast(ubyte)~val;
|
||||
}
|
||||
|
||||
|
||||
|
@ -10,12 +10,12 @@ if (isCpu!T)
|
||||
{
|
||||
static if (isNMOS!T)
|
||||
enum REG_OPS = cast(ubyte[])
|
||||
x"0A 18 1A 2A 38 3A 4A 58 5A 6A 78 7A 8A 88 98 9A
|
||||
x"0A 18 1A 2A 38 3A 4A 58 5A 6A 78 7A 88 8A 98 9A
|
||||
A8 AA B8 BA C8 CA D8 DA E8 EA F8 FA";
|
||||
else
|
||||
enum REG_OPS = cast(ubyte[])
|
||||
x"0A 18 1A 2A 38 3A 4A 58 6A 78 8A 88 98 9A
|
||||
A8 AA B8 BA C8 CA D8 E8 EA F8";
|
||||
x"0A 18 1A 2A 38 3A 4A 58 6A 78 88 8A 98 9A A8 AA
|
||||
B8 BA C8 CA D8 E8 EA F8";
|
||||
}
|
||||
|
||||
|
||||
|
132
test/test_bus.d
132
test/test_bus.d
@ -77,14 +77,14 @@ if (isCpu!T)
|
||||
auto opcode = mem[pc];
|
||||
assert(IMM_OPS!T.canFind(opcode));
|
||||
|
||||
bool decimal = isCMOS!T && isStrict!T && getFlag(cpu, Flag.D) &&
|
||||
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(
|
||||
[Bus(Action.READ, pc+2)]);
|
||||
If!decimal(If!(isStrict!T)(
|
||||
[Bus(Action.READ, pc+2)]));
|
||||
}
|
||||
|
||||
|
||||
@ -287,6 +287,7 @@ if (isCpu!T && isNMOS!T)
|
||||
auto opcode = mem[pc];
|
||||
assert(HLT_OPS!T.canFind(opcode));
|
||||
|
||||
cycles = 1;
|
||||
return [Bus(Action.READ, pc)];
|
||||
}
|
||||
|
||||
@ -346,14 +347,14 @@ if (isCpu!T)
|
||||
bool rmw = RMW_OPS!T.canFind(opcode);
|
||||
bool write = !rmw && WRITE_OPS!T.canFind(opcode);
|
||||
bool read = !rmw && !write;
|
||||
bool decimal = isCMOS!T && isStrict!T && getFlag(cpu, Flag.D) &&
|
||||
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(
|
||||
[Bus(Action.READ, pc + opLen)])) ~
|
||||
If!decimal(If!(isStrict!T)(
|
||||
[Bus(Action.READ, pc + opLen)]))) ~
|
||||
If!write(
|
||||
[Bus(Action.WRITE, addr)]) ~
|
||||
If!rmw(
|
||||
@ -612,93 +613,116 @@ template timesetup_t(T)
|
||||
}
|
||||
|
||||
|
||||
// Tests the bus access patterns and cycles taken for a given opcode.
|
||||
void test_opcode_timing(T)(ubyte opcode)
|
||||
alias void delegate(int, const Bus[], int, const Bus[], ubyte, string)
|
||||
busreport;
|
||||
|
||||
|
||||
auto run_timing_test(T)(timesetup_t!T expect, busreport report)
|
||||
{
|
||||
addrsetup_t!T[] function(ubyte) setups1;
|
||||
datasetup_t!T[] function(ubyte) setups2;
|
||||
mixin(getMemSetup!T());
|
||||
|
||||
timesetup_t!T expected;
|
||||
mixin(getExpected!T());
|
||||
|
||||
auto funcs1 = setups1(opcode);
|
||||
string name1;
|
||||
foreach(func1; funcs1)
|
||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||
string msg, TestSetup* next)
|
||||
{
|
||||
ushort addr;
|
||||
int cycles;
|
||||
auto cpu = new T();
|
||||
auto block1 = func1(cpu, addr, name1);
|
||||
auto mem = TestMemory(block1);
|
||||
connectMem(cpu, mem);
|
||||
auto exp = expected(cpu, mem, cycles);
|
||||
exp = exp ~ new Bus[8 - exp.length];
|
||||
auto actual = recordBus(cpu);
|
||||
auto actualCycles = recordCycles(cpu);
|
||||
// XXX debug
|
||||
write(format("Testing %s (%0.2X) -- ", name1, opcode));
|
||||
try
|
||||
{
|
||||
runOneOpcode(cpu);
|
||||
}
|
||||
catch (TestException e) // possibly not related to timing
|
||||
{
|
||||
// XXX wrap
|
||||
throw e;
|
||||
}
|
||||
if (actual != exp)
|
||||
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 != cycles)
|
||||
if (actualCycles != expectCycles)
|
||||
{
|
||||
// XXX make error message, throw
|
||||
}
|
||||
if (actual == exp && actualCycles == cycles)
|
||||
writeln("OK");
|
||||
if (actualBus == expectBus && actualCycles == expectCycles) {}
|
||||
else
|
||||
{
|
||||
write(format("[%0.2X] %s", opcode, msg));
|
||||
writeln();
|
||||
writeln(actualCycles, " ", cycles);
|
||||
writeln(actual);
|
||||
writeln(exp);
|
||||
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);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
auto report = report_timing_debug();
|
||||
|
||||
alias CPU!("65C02", false, false) T1;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T1(cast(ubyte)op);
|
||||
test_opcode_timing!T1(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("65C02", true, false) T2;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T2(cast(ubyte)op);
|
||||
test_opcode_timing!T2(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("6502", false, false) T3;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T3(cast(ubyte)op);
|
||||
test_opcode_timing!T3(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("6502", true, false) T4;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T4(cast(ubyte)op);
|
||||
test_opcode_timing!T4(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("65C02", false, true) T5;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T5(cast(ubyte)op);
|
||||
test_opcode_timing!T5(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("65C02", true, true) T6;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T6(cast(ubyte)op);
|
||||
test_opcode_timing!T6(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("6502", false, true) T7;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T7(cast(ubyte)op);
|
||||
test_opcode_timing!T7(cast(ubyte)op, report);
|
||||
|
||||
alias CPU!("6502", true, true) T8;
|
||||
for (int op = 0x00; op < 0x100; op++)
|
||||
test_opcode_timing!T8(cast(ubyte)op);
|
||||
test_opcode_timing!T8(cast(ubyte)op, report);
|
||||
}
|
||||
|
104
test/test_func.d
Normal file
104
test/test_func.d
Normal file
@ -0,0 +1,104 @@
|
||||
module test.test_func;
|
||||
|
||||
|
||||
import std.string;
|
||||
|
||||
import test.base, test.cpu, test.opcodes;
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
unittest
|
||||
{
|
||||
auto report = report_debug();
|
||||
|
||||
alias CPU!("65C02", false, false) T1;
|
||||
foreach (opcode; 0..255)
|
||||
test_one_opcode!T1(cast(ubyte)opcode, report);
|
||||
|
||||
alias CPU!("6502", false, false) T2;
|
||||
foreach (opcode; 0..255)
|
||||
test_one_opcode!T2(cast(ubyte)opcode, report);
|
||||
}
|
Loading…
Reference in New Issue
Block a user