Added basic test framework
This commit is contained in:
parent
4b27ddbe75
commit
412b1affad
|
@ -0,0 +1,255 @@
|
|||
module test.base;
|
||||
|
||||
|
||||
import std.conv, std.exception, std.string, std.traits;
|
||||
|
||||
|
||||
public import d6502.nmosundoc : NmosUndoc;
|
||||
public import d6502.cmos : Cmos;
|
||||
|
||||
|
||||
template isNMOS(T)
|
||||
{
|
||||
enum isNMOS = __traits(hasMember, T, "_isNMOS");
|
||||
}
|
||||
|
||||
template isCMOS(T)
|
||||
{
|
||||
enum isCMOS = __traits(hasMember, T, "_isCMOS");
|
||||
}
|
||||
|
||||
template isCpu(T)
|
||||
{
|
||||
enum isCpu = __traits(hasMember, T, "_isCpuBase");
|
||||
}
|
||||
|
||||
template isStrict(T)
|
||||
{
|
||||
enum isStrict = __traits(hasMember, T, "_isStrict");
|
||||
}
|
||||
|
||||
template isCumulative(T)
|
||||
{
|
||||
enum isCumulative = __traits(hasMember, T, "_isCumulative");
|
||||
}
|
||||
|
||||
|
||||
class TestException : Exception { this(string msg) { super(msg); } }
|
||||
|
||||
|
||||
struct Block
|
||||
{
|
||||
ushort base;
|
||||
ubyte[] data;
|
||||
}
|
||||
|
||||
/* Emulates zero page, stack, and 3 additional pages starting at a
|
||||
* user-defined address. Accesses outside the defined address space
|
||||
* raise an exception.
|
||||
*/
|
||||
struct TestMemory
|
||||
{
|
||||
ubyte[0x200] data1;
|
||||
ubyte[0x300] data2;
|
||||
immutable ushort data2_base;
|
||||
immutable size_t data2_max;
|
||||
|
||||
this(Block[] blocks ...)
|
||||
{
|
||||
size_t last_1, last_2;
|
||||
foreach (block; blocks)
|
||||
{
|
||||
auto base = block.base;
|
||||
auto data = block.data;
|
||||
if (base < 0x200)
|
||||
{
|
||||
enforce(base >= last_1,
|
||||
format("Overlapping address %0.4x", base));
|
||||
enforce(base + data.length <= 0x200,
|
||||
format("Address out of bounds %0.4x", base));
|
||||
last_1 = base + data.length;
|
||||
data1[base..last_1] = data[];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!data2_base)
|
||||
{
|
||||
last_2 = data2_base = base;
|
||||
data2_max = base + 0x300;
|
||||
}
|
||||
enforce(base >= last_2,
|
||||
format("Overlapping address %0.4x", base));
|
||||
enforce(base + data.length <= data2_max,
|
||||
format("Address out of bounds %0.4x", base));
|
||||
last_2 = base + data.length;
|
||||
data2[base-data2_base..last_2-data2_base] = data[];
|
||||
}
|
||||
}
|
||||
enforce(data2_base, "Missing memory > 0x0200");
|
||||
}
|
||||
|
||||
ubyte read(ushort addr)
|
||||
{
|
||||
if (addr < 0x0200)
|
||||
return data1[addr];
|
||||
else if (addr >= data2_base && addr < data2_max)
|
||||
return data2[addr - data2_base];
|
||||
else
|
||||
throw new TestException(format("read %0.4x", addr));
|
||||
}
|
||||
|
||||
void write(ushort addr, ubyte val)
|
||||
{
|
||||
if (addr < 0x0200)
|
||||
data1[addr] = val;
|
||||
else if (addr >= data2_base && addr < data2_max)
|
||||
data2[addr - data2_base] = val;
|
||||
else
|
||||
throw new TestException(format("write %0.4x", addr));
|
||||
}
|
||||
|
||||
ubyte opIndex(size_t i1)
|
||||
{
|
||||
auto addr = cast(ushort)i1;
|
||||
enforce(addr < 0x0200 || (addr >= data2_base && addr < data2_max),
|
||||
"Read out of bounds");
|
||||
return read(addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
T makeCpu(T)(ref TestMemory mem)
|
||||
if (isCpu!T)
|
||||
{
|
||||
static if (isCumulative!T) void tick(int cycles) {}
|
||||
else void tick() {}
|
||||
|
||||
auto cpu = new T();
|
||||
cpu.memoryRead = &mem.read;
|
||||
cpu.memoryWrite = &mem.write;
|
||||
cpu.tick = &tick;
|
||||
return cpu;
|
||||
}
|
||||
|
||||
|
||||
struct Ref(T)
|
||||
if (isPointer!T)
|
||||
{
|
||||
private const(T) data;
|
||||
this(T ptr) { data = ptr; }
|
||||
auto deref() { return *data; }
|
||||
alias deref this;
|
||||
|
||||
string toString () const { return format("%s", *data); }
|
||||
}
|
||||
|
||||
auto constRef(T)(T ptr)
|
||||
if (isPointer!T)
|
||||
{
|
||||
return Ref!(const(T))(ptr);
|
||||
}
|
||||
|
||||
auto recordCycles(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto cycles = new int;
|
||||
auto wrappedTick = cpu.tick;
|
||||
|
||||
static if (isCumulative!T)
|
||||
{
|
||||
void tick(int cyc)
|
||||
{
|
||||
(*cycles) += cyc;
|
||||
wrappedTick(cyc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
void tick()
|
||||
{
|
||||
(*cycles)++;
|
||||
wrappedTick();
|
||||
}
|
||||
}
|
||||
cpu.tick = &tick;
|
||||
|
||||
return constRef(cycles);
|
||||
}
|
||||
|
||||
|
||||
enum Action : ushort { NONE, READ, WRITE }
|
||||
|
||||
struct Bus
|
||||
{
|
||||
Action action;
|
||||
ushort addr;
|
||||
|
||||
string toString() const
|
||||
{
|
||||
return format("Bus(%s, %0.4X)", to!string(action), addr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
const(Bus[]) recordBus(T)(T cpu, int actions = 8)
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto record = new Bus[actions];
|
||||
int c;
|
||||
|
||||
enforce(cpu.memoryRead !is null && cpu.memoryWrite !is null);
|
||||
auto wrappedRead = cpu.memoryRead;
|
||||
auto wrappedWrite = cpu.memoryWrite;
|
||||
|
||||
ubyte read(ushort addr)
|
||||
{
|
||||
if (c == actions)
|
||||
throw new TestException(
|
||||
format("cannot record more than %d actions", actions));
|
||||
record[c++] = Bus(Action.READ, addr);
|
||||
return wrappedRead(addr);
|
||||
}
|
||||
|
||||
void write(ushort addr, ubyte val)
|
||||
{
|
||||
if (c == actions)
|
||||
throw new TestException(
|
||||
format("cannot record more than %d actions", actions));
|
||||
record[c++] = Bus(Action.WRITE, addr);
|
||||
wrappedWrite(addr, val);
|
||||
}
|
||||
|
||||
cpu.memoryRead = &read;
|
||||
cpu.memoryWrite = &write;
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
|
||||
class StopException : Exception { this(string msg) { super(msg); } }
|
||||
|
||||
void runUntilBRK(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
{
|
||||
enforce(cpu.memoryRead !is null);
|
||||
auto wrappedRead = cpu.memoryRead;
|
||||
|
||||
ubyte read(ushort addr)
|
||||
{
|
||||
if (addr == 0xFFFE) throw new StopException("BRK");
|
||||
return wrappedRead(addr);
|
||||
}
|
||||
|
||||
cpu.memoryRead = &read;
|
||||
|
||||
try { cpu.run(true); } catch (StopException e) {}
|
||||
}
|
||||
|
||||
|
||||
void runOneOpcode(T)(T cpu)
|
||||
if (isCpu!T)
|
||||
{
|
||||
cpu.run(false);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
-unittest -I.. -I../src -inline -release -O
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
module test.test_decimal;
|
||||
|
||||
import std.stdio;
|
||||
import test.base;
|
||||
|
||||
|
||||
void testDecimalMode(T)()
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto mem = TestMemory(Block(0x8000, cast(ubyte[])
|
||||
/* 8000 JMP TEST */ x"4C 14 80"
|
||||
/* 8003 ERROR DB 0 */ x"00"
|
||||
/* 8004 N1 DB 0 */ x"00"
|
||||
/* 8005 N2 DB 0 */ x"00"
|
||||
/* 8006 N1H DB 0 */ x"00"
|
||||
/* 8007 N1L DB 0 */ x"00"
|
||||
/* 8008 N2H DW 0 */ x"0000"
|
||||
/* 800A N2L DB 0 */ x"00"
|
||||
/* 800B DA DB 0 */ x"00"
|
||||
/* 800C DNVZC DB 0 */ x"00"
|
||||
/* 800D HA DB 0 */ x"00"
|
||||
/* 800E HNVZC DB 0 */ x"00"
|
||||
/* 800F AR DB 0 */ x"00"
|
||||
/* 8010 NF DB 0 */ x"00"
|
||||
/* 8011 VF DB 0 */ x"00"
|
||||
/* 8012 ZF DB 0 */ x"00"
|
||||
/* 8013 CF DB 0 */ x"00"
|
||||
/* 8014 TEST LDY #1 */ x"A0 01"
|
||||
/* 8016 STY ERROR */ x"8C 03 80"
|
||||
/* 8019 LDA #0 */ x"A9 00"
|
||||
/* 801B STA N1 */ x"8D 04 80"
|
||||
/* 801E STA N2 */ x"8D 05 80"
|
||||
/* 8021 LOOP1 LDA N2 */ x"AD 05 80"
|
||||
/* 8024 AND #$0F */ x"29 0F"
|
||||
/* 8026 STA N2L */ x"8D 0A 80"
|
||||
/* 8029 LDA N2 */ x"AD 05 80"
|
||||
/* 802C AND #$F0 */ x"29 F0"
|
||||
/* 802E STA N2H */ x"8D 08 80"
|
||||
/* 8031 ORA #$0F */ x"09 0F"
|
||||
/* 8033 STA N2H+1 */ x"8D 09 80"
|
||||
/* 8036 LOOP2 LDA N1 */ x"AD 04 80"
|
||||
/* 8039 AND #$0F */ x"29 0F"
|
||||
/* 803B STA N1L */ x"8D 07 80"
|
||||
/* 803E LDA N1 */ x"AD 04 80"
|
||||
/* 8041 AND #$F0 */ x"29 F0"
|
||||
/* 8043 STA N1H */ x"8D 06 80"
|
||||
/* 8045 JSR ADD */ x"20 6F 80"
|
||||
/* 8049 JSR A6502 */ x"20 58 81"
|
||||
/* 804C JSR COMPARE */ x"20 29 81"
|
||||
/* 804F BNE DONE */ x"D0 1D"
|
||||
/* 8051 JSR SUB */ x"20 C2 80"
|
||||
/* 8054 JSR S6502 */ x"20 65 81"
|
||||
/* 8057 JSR COMPARE */ x"20 29 81"
|
||||
/* 805A BNE DONE */ x"D0 12"
|
||||
/* 805C INC N1 */ x"EE 04 80"
|
||||
/* 805F BNE LOOP2 */ x"D0 D5"
|
||||
/* 8061 INC N2 */ x"EE 05 80"
|
||||
/* 8064 BNE LOOP1 */ x"D0 BB"
|
||||
/* 8066 DEY */ x"88"
|
||||
/* 8067 BPL LOOP1 */ x"10 B8"
|
||||
/* 8069 LDA #0 */ x"A9 00"
|
||||
/* 806B STA ERROR */ x"8D 03 80"
|
||||
/* 806E DONE BRK */ x"00"
|
||||
/* 806F ADD SED */ x"F8"
|
||||
/* 8070 CPY #1 */ x"C0 01"
|
||||
/* 8072 LDA N1 */ x"AD 04 80"
|
||||
/* 8075 ADC N2 */ x"6D 05 80"
|
||||
/* 8078 STA DA */ x"8D 0B 80"
|
||||
/* 807B PHP */ x"08"
|
||||
/* 807C PLA */ x"68"
|
||||
/* 807D STA DNVZC */ x"8D 0C 80"
|
||||
/* 8080 CLD */ x"D8"
|
||||
/* 8081 CPY #1 */ x"C0 01"
|
||||
/* 8083 LDA N1 */ x"AD 04 80"
|
||||
/* 8086 ADC N2 */ x"6D 05 80"
|
||||
/* 8089 STA HA */ x"8D 0D 80"
|
||||
/* 808C PHP */ x"08"
|
||||
/* 808D PLA */ x"68"
|
||||
/* 808E STA HNVZC */ x"8D 0E 80"
|
||||
/* 8091 CPY #1 */ x"C0 01"
|
||||
/* 8093 LDA N1L */ x"AD 07 80"
|
||||
/* 8096 ADC N2L */ x"6D 0A 80"
|
||||
/* 8099 CMP #$0A */ x"C9 0A"
|
||||
/* 809B LDX #0 */ x"A2 00"
|
||||
/* 809D BCC A1 */ x"90 06"
|
||||
/* 809F INX */ x"E8"
|
||||
/* 80A0 ADC #5 */ x"69 05"
|
||||
/* 80A2 AND #$0F */ x"29 0F"
|
||||
/* 80A4 SEC */ x"38"
|
||||
/* 80A5 A1 ORA N1H */ x"0D 06 80"
|
||||
/* 80A8 ADC N2H,X */ x"7D 08 80"
|
||||
/* 80AB PHP */ x"08"
|
||||
/* 80AC BCS A2 */ x"B0 04"
|
||||
/* 80AE CMP #$A0 */ x"C9 A0"
|
||||
/* 80B0 BCC A3 */ x"90 03"
|
||||
/* 80B2 A2 ADC #$5F */ x"69 5F"
|
||||
/* 80B4 SEC */ x"38"
|
||||
/* 80B5 A3 STA AR */ x"8D 0F 80"
|
||||
/* 80B8 PHP */ x"08"
|
||||
/* 80B9 PLA */ x"68"
|
||||
/* 80BA STA CF */ x"8D 13 80"
|
||||
/* 80BD PLA */ x"68"
|
||||
/* 80BE STA VF */ x"8D 11 80"
|
||||
/* 80C1 RTS */ x"60"
|
||||
/* 80C2 SUB SED */ x"F8"
|
||||
/* 80C3 CPY #1 */ x"C0 01"
|
||||
/* 80C5 LDA N1 */ x"AD 04 80"
|
||||
/* 80C8 SBC N2 */ x"ED 05 80"
|
||||
/* 80CB STA DA */ x"8D 0B 80"
|
||||
/* 80CE PHP */ x"08"
|
||||
/* 80CF PLA */ x"68"
|
||||
/* 80D0 STA DNVZC */ x"8D 0C 80"
|
||||
/* 80D3 CLD */ x"D8"
|
||||
/* 80D4 CPY #1 */ x"C0 01"
|
||||
/* 80D6 LDA N1 */ x"AD 04 80"
|
||||
/* 80D9 SBC N2 */ x"ED 05 80"
|
||||
/* 80DC STA HA */ x"8D 0D 80"
|
||||
/* 80DF PHP */ x"08"
|
||||
/* 80E0 PLA */ x"68"
|
||||
/* 80E1 STA HNVZC */ x"8D 0E 80"
|
||||
/* 80E4 RTS */ x"60"
|
||||
/* 80E5 SUB1 CPY #1 */ x"C0 01"
|
||||
/* 80E7 LDA N1L */ x"AD 07 80"
|
||||
/* 80EA SBC N2L */ x"ED 0A 80"
|
||||
/* 80ED LDX #0 */ x"A2 00"
|
||||
/* 80EF BCS S11 */ x"B0 06"
|
||||
/* 80F1 INX */ x"E8"
|
||||
/* 80F2 SBC #5 */ x"E9 05"
|
||||
/* 80F4 AND #$0F */ x"29 0F"
|
||||
/* 80F6 CLC */ x"18"
|
||||
/* 80F7 S11 ORA N1H */ x"0D 06 80"
|
||||
/* 80FA SBC N2H,X */ x"FD 08 80"
|
||||
/* 80FD BCS S12 */ x"B0 02"
|
||||
/* 80FF SBC #$5F */ x"E9 5F"
|
||||
/* 8101 S12 STA AR */ x"8D 0F 80"
|
||||
/* 8104 RTS */ x"60"
|
||||
/* 8105 SUB2 CPY #1 */ x"C0 01"
|
||||
/* 8107 LDA N1L */ x"AD 07 80"
|
||||
/* 810A SBC N2L */ x"ED 0A 80"
|
||||
/* 810D LDX #0 */ x"A2 00"
|
||||
/* 810F BCS S21 */ x"B0 04"
|
||||
/* 8111 INX */ x"E8"
|
||||
/* 8112 AND #$0F */ x"29 0F"
|
||||
/* 8114 CLC */ x"18"
|
||||
/* 8115 S21 ORA N1H */ x"0D 06 80"
|
||||
/* 8118 SBC N2H,X */ x"FD 08 80"
|
||||
/* 811B BCS S22 */ x"B0 02"
|
||||
/* 811D SBC #$5F */ x"E9 5F"
|
||||
/* 811F S22 CPX #0 */ x"E0 00"
|
||||
/* 8121 BEQ S23 */ x"F0 02"
|
||||
/* 8123 SBC #6 */ x"E9 06"
|
||||
/* 8125 S23 STA AR */ x"8D 0F 80"
|
||||
/* 8128 RTS */ x"60"
|
||||
/* 8129 COMPARE LDA DA */ x"AD 0B 80"
|
||||
/* 812C CMP AR */ x"CD 0F 80"
|
||||
/* 812F BNE C1 */ x"D0 26"
|
||||
/* 8131 LDA DNVZC */ x"AD 0C 80"
|
||||
/* 8134 EOR NF */ x"4D 10 80"
|
||||
/* 8137 AND #$80 */ x"29 80"
|
||||
/* 8139 BNE C1 */ x"D0 1C"
|
||||
/* 813B LDA DNVZC */ x"AD 0C 80"
|
||||
/* 813E EOR VF */ x"4D 11 80"
|
||||
/* 8141 AND #$40 */ x"29 40"
|
||||
/* 8143 BNE C1 */ x"D0 12"
|
||||
/* 8145 LDA DNVZC */ x"AD 0C 80"
|
||||
/* 8148 EOR ZF */ x"4D 12 80"
|
||||
/* 814B AND #2 */ x"29 02"
|
||||
/* 814D BNE C1 */ x"D0 08"
|
||||
/* 814F LDA DNVZC */ x"AD 0C 80"
|
||||
/* 8152 EOR CF */ x"4D 13 80"
|
||||
/* 8155 AND #1 */ x"29 01"
|
||||
/* 8157 C1 RTS */ x"60"
|
||||
/* 8158 A6502 LDA VF */ x"AD 11 80"
|
||||
/* 815B STA NF */ x"8D 10 80"
|
||||
/* 815E LDA HNVZC */ x"AD 0E 80"
|
||||
/* 8161 STA ZF */ x"8D 12 80"
|
||||
/* 8164 RTS */ x"60"
|
||||
/* 8165 S6502 JSR SUB1 */ x"20 E5 80"
|
||||
/* 8168 LDA HNVZC */ x"AD 0E 80"
|
||||
/* 816B STA NF */ x"8D 10 80"
|
||||
/* 816E STA VF */ x"8D 11 80"
|
||||
/* 8171 STA ZF */ x"8D 12 80"
|
||||
/* 8174 STA CF */ x"8D 13 80"
|
||||
/* 8177 RTS */ x"60"
|
||||
/* 8178 A65C02 LDA AR */ x"AD 0F 80"
|
||||
/* 817B PHP */ x"08"
|
||||
/* 817C PLA */ x"68"
|
||||
/* 817D STA NF */ x"8D 10 80"
|
||||
/* 8180 STA ZF */ x"8D 12 80"
|
||||
/* 8183 RTS */ x"60"
|
||||
/* 8184 S65C02 JSR SUB2 */ x"20 05 81"
|
||||
/* 8187 LDA AR */ x"AD 0F 80"
|
||||
/* 818A PHP */ x"08"
|
||||
/* 818B PLA */ x"68"
|
||||
/* 818C STA NF */ x"8D 10 80"
|
||||
/* 818F STA ZF */ x"8D 12 80"
|
||||
/* 8192 LDA HNVZC */ x"AD 0E 80"
|
||||
/* 8195 STA VF */ x"8D 11 80"
|
||||
/* 8198 STA CF */ x"8D 13 80"
|
||||
/* 819B RTS */ x"60"
|
||||
));
|
||||
|
||||
// Patch the JSRs at $804A/$8055
|
||||
static if (isNMOS!T)
|
||||
{
|
||||
mem.write(0x804A, 0x58);
|
||||
mem.write(0x8055, 0x65);
|
||||
}
|
||||
else
|
||||
{
|
||||
mem.write(0x804A, 0x78);
|
||||
mem.write(0x8055, 0x84);
|
||||
}
|
||||
|
||||
auto cpu = makeCpu!T(mem);
|
||||
cpu.programCounter = 0x8000;
|
||||
runUntilBRK(cpu);
|
||||
if (mem[0x8003])
|
||||
throw new TestException("failed decimal mode " ~ T.stringof);
|
||||
}
|
||||
|
||||
|
||||
unittest
|
||||
{
|
||||
writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.no)");
|
||||
testDecimalMode!(NmosUndoc!(false, false))();
|
||||
writeln("Testing decimal mode, CMOS(Strict.no, Cumulative.no)");
|
||||
testDecimalMode!(Cmos!(false, false))();
|
||||
|
||||
/+
|
||||
writeln("Testing decimal mode, NMOS(Strict.no, Cumulative.yes)");
|
||||
testDecimalMode!(NmosUndoc!(false, true))();
|
||||
writeln("Testing decimal mode, CMOS(Strict.no, Cumulative.yes)");
|
||||
testDecimalMode!(Cmos!(false, true))();
|
||||
|
||||
writeln("Testing decimal mode, NMOS(Strict.yes, Cumulative.no)");
|
||||
testDecimalMode!(NmosUndoc!(true, false))();
|
||||
writeln("Testing decimal mode, CMOS(Strict.yes, Cumulative.no)");
|
||||
testDecimalMode!(Cmos!(true, false))();
|
||||
|
||||
writeln("Testing decimal mode, NMOS(Strict.yes, Cumulative.yes)");
|
||||
testDecimalMode!(NmosUndoc!(true, true))();
|
||||
writeln("Testing decimal mode, CMOS(Strict.yes, Cumulative.yes)");
|
||||
testDecimalMode!(Cmos!(true, true))();
|
||||
+/
|
||||
}
|
Loading…
Reference in New Issue