Interrupt handling
This commit is contained in:
parent
6ac568742e
commit
c663b46003
|
@ -73,7 +73,7 @@ string OpMethods(string chip)
|
|||
foreach (op; 0..256)
|
||||
{
|
||||
ret ~= "final void opcode_" ~ Hex2(op) ~ "()\n{\n" ~
|
||||
If!(cumulative)("int cycles = 1;\n") ~
|
||||
// If!(cumulative)("int cycles = 1;\n") ~
|
||||
OpBody(op, chip) ~ "}\n";
|
||||
}
|
||||
return ret;
|
||||
|
@ -141,7 +141,7 @@ string OpBody(int op, string chip)
|
|||
final switch (opName(op, chip))
|
||||
{
|
||||
case "BRK":
|
||||
ret ~= Break();
|
||||
ret ~= Break(chip);
|
||||
break;
|
||||
case "RTI":
|
||||
ret ~= RetInt();
|
||||
|
@ -192,7 +192,7 @@ string OpBody(int op, string chip)
|
|||
ret ~= SetFlag(_C);
|
||||
break;
|
||||
case "CLI":
|
||||
ret ~= ClearFlag(_I);
|
||||
ret ~= ClearInt(chip);
|
||||
break;
|
||||
case "SEI":
|
||||
ret ~= SetFlag(_I);
|
||||
|
@ -240,10 +240,10 @@ string OpBody(int op, string chip)
|
|||
ret ~= Inc(_Y);
|
||||
break;
|
||||
case "PHP":
|
||||
ret ~= Push(Attr("statusToByte()"));
|
||||
ret ~= Push("statusToByte()");
|
||||
break;
|
||||
case "PLP":
|
||||
ret ~= PullStatus();
|
||||
ret ~= PullSReg(chip);
|
||||
break;
|
||||
case "PLA":
|
||||
ret ~= PullReg(_A);
|
||||
|
@ -418,13 +418,24 @@ string OpBody(int op, string chip)
|
|||
}
|
||||
|
||||
|
||||
string Break()
|
||||
string Break(string chip)
|
||||
{
|
||||
bool nmos = (chip == "6502");
|
||||
bool cmos = !nmos;
|
||||
|
||||
return IncPC() ~
|
||||
PushPC() ~
|
||||
Push(Attr("statusToByte()")) ~
|
||||
Push("statusToByte()") ~
|
||||
SetFlag(_I) ~
|
||||
ReadWord(_PC, "IRQ_VECTOR");
|
||||
If!(cmos)(ClearFlag(_D)) ~
|
||||
"ushort ivec = IRQ_VECTOR;\n" ~
|
||||
If!(nmos)(
|
||||
"if (signals.nmiLow)\n{\n" ~
|
||||
"signals.nmiLow = false;\n" ~
|
||||
"signals.updateSignals();\n" ~
|
||||
"ivec = NMI_VECTOR;\n" ~
|
||||
"}\n") ~
|
||||
ReadWord(_PC, "ivec");
|
||||
}
|
||||
|
||||
|
||||
|
@ -1010,6 +1021,19 @@ string CheckShortcut(string base, string addr, string chip, int exCyc)
|
|||
If!(exCyc)("else\n{\n" ~ Peek("address") ~ "}\n");
|
||||
}
|
||||
|
||||
string CheckBranchShortcut(string base, string addr, string chip, int exCyc)
|
||||
{
|
||||
bool nmos = (chip == "6502");
|
||||
|
||||
return "ushort guess = (" ~ base ~ " & 0xFF00) | cast(ubyte)" ~ addr ~ ";\n" ~
|
||||
"if (guess != " ~ addr ~ ")\n{\n" ~
|
||||
If!(nmos)(Peek("guess"),
|
||||
Peek(_PC)) ~
|
||||
"}\nelse\n{\n" ~
|
||||
If!(nmos)("idelay = ndelay = true;\n") ~
|
||||
"}\n";
|
||||
}
|
||||
|
||||
|
||||
string ReadInto(string var, string action, string addr)
|
||||
{
|
||||
|
@ -1114,13 +1138,21 @@ string DecSP()
|
|||
return "--" ~ _S ~ ";\n";
|
||||
}
|
||||
|
||||
string PullSReg(string chip)
|
||||
{
|
||||
bool nmos = (chip == "6502");
|
||||
|
||||
return If!(nmos)("idelay = " ~ _I ~ ";\n") ~
|
||||
PullStatus() ~
|
||||
If!(nmos)("idelay = idelay & !" ~ _I ~ ";\n");
|
||||
}
|
||||
|
||||
string PullStatus()
|
||||
{
|
||||
return Peek(STACK) ~
|
||||
IncSP() ~
|
||||
Tick() ~
|
||||
Attr("statusFromByte") ~ "(" ~
|
||||
ReadRaw(STACK) ~ ");\n";
|
||||
"statusFromByte(" ~ ReadRaw(STACK) ~ ");\n";
|
||||
}
|
||||
|
||||
string PullInto(string var)
|
||||
|
@ -1171,6 +1203,14 @@ string ClearFlag(string flag)
|
|||
return flag ~ " = false;\n";
|
||||
}
|
||||
|
||||
string ClearInt(string chip)
|
||||
{
|
||||
bool nmos = (chip == "6502");
|
||||
|
||||
return If!(nmos)("idelay = " ~ _I ~ ";\n") ~
|
||||
_I ~ " = false;\n";
|
||||
}
|
||||
|
||||
string UpdateFlag(string flag, string val)
|
||||
{
|
||||
return flag ~ " = (" ~ val ~ ");\n";
|
||||
|
|
118
src/cpu/d6502.d
118
src/cpu/d6502.d
|
@ -40,6 +40,44 @@ template is65C02(T)
|
|||
}
|
||||
|
||||
|
||||
final class Signals
|
||||
{
|
||||
private:
|
||||
bool active;
|
||||
bool resetLow, nmiLow, irqLow;
|
||||
|
||||
final void updateSignals()
|
||||
{
|
||||
active = resetLow || nmiLow || irqLow;
|
||||
}
|
||||
|
||||
public:
|
||||
final void triggerReset()
|
||||
{
|
||||
resetLow = true;
|
||||
updateSignals();
|
||||
}
|
||||
|
||||
final void triggerNMI()
|
||||
{
|
||||
nmiLow = true;
|
||||
updateSignals();
|
||||
}
|
||||
|
||||
final void assertIRQ()
|
||||
{
|
||||
irqLow = true;
|
||||
updateSignals();
|
||||
}
|
||||
|
||||
final void deassertIRQ()
|
||||
{
|
||||
irqLow = false;
|
||||
updateSignals();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class Cpu(string chip, MEM, CLK)
|
||||
if (__traits(compiles, {
|
||||
MEM m; ubyte val; ushort addr;
|
||||
|
@ -64,10 +102,20 @@ if (__traits(compiles, {
|
|||
ubyte N, Z;
|
||||
bool V, D, I, C;
|
||||
|
||||
bool keepRunning;
|
||||
Signals signals;
|
||||
|
||||
static if (_chip == "6502")
|
||||
{
|
||||
// To handle NMI/IRQ delay after 3-cycle branch, and after CLI/PLP.
|
||||
private bool ndelay, idelay, delay_handled;
|
||||
}
|
||||
|
||||
version(Cumulative) { private int cycles; }
|
||||
|
||||
version(OpDelegates)
|
||||
{
|
||||
mixin(OpArrayDef());
|
||||
version(Cumulative) { int cycles; }
|
||||
ushort address, base;
|
||||
ubyte data;
|
||||
}
|
||||
|
@ -76,6 +124,7 @@ if (__traits(compiles, {
|
|||
{
|
||||
this.memory = memory;
|
||||
this.clock = clock;
|
||||
signals = new Signals();
|
||||
|
||||
version(OpDelegates) mixin(OpArrayInit());
|
||||
}
|
||||
|
@ -101,25 +150,22 @@ if (__traits(compiles, {
|
|||
(N & 0x80);
|
||||
}
|
||||
|
||||
bool keepRunning;
|
||||
bool signalActive;
|
||||
bool resetLow;
|
||||
|
||||
final void run(bool continuous)
|
||||
{
|
||||
keepRunning = continuous;
|
||||
ubyte opcode;
|
||||
static if (!opArray)
|
||||
{
|
||||
version(Cumulative) { int cycles; }
|
||||
ushort address, base;
|
||||
ubyte data;
|
||||
}
|
||||
do
|
||||
{
|
||||
version(Cumulative) { static if (!opArray) cycles = 1; }
|
||||
version(Cumulative) { cycles = 0; }
|
||||
if (signals.active) handleSignals();
|
||||
static if (_chip == "6502") { idelay = ndelay = false; }
|
||||
version(Cumulative) { cycles = 1; }
|
||||
else { clock.tick(); }
|
||||
if (signalActive) handleSignals();
|
||||
opcode = memory.read(PC++);
|
||||
mixin(OpExecute(_chip));
|
||||
} while (keepRunning);
|
||||
|
@ -128,31 +174,77 @@ if (__traits(compiles, {
|
|||
// TODO: irq/nmi
|
||||
void handleSignals()
|
||||
{
|
||||
if (resetLow) doReset();
|
||||
// XXX fix when more than one signal
|
||||
signalActive = resetLow;
|
||||
if (signals.resetLow) { doReset(); return; }
|
||||
|
||||
static if (_chip == "6502")
|
||||
{
|
||||
// Handle the case where NMI/IRQ are delayed for one
|
||||
// instruction after a 3-cycle branch opcode, a CLI, or a PLP.
|
||||
if (!delay_handled && (ndelay || (idelay && !signals.nmiLow)))
|
||||
{
|
||||
delay_handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (signals.nmiLow)
|
||||
doNMI();
|
||||
else if (signals.irqLow && !I)
|
||||
doIRQ();
|
||||
|
||||
static if (_chip == "6502") { delay_handled = false; }
|
||||
}
|
||||
|
||||
void doReset()
|
||||
{
|
||||
mixin(Tick() ~ Tick() ~
|
||||
mixin(Peek("PC") ~ Peek("PC") ~
|
||||
Peek(STACK) ~ DecSP() ~
|
||||
Peek(STACK) ~ DecSP() ~
|
||||
Peek(STACK) ~ DecSP());
|
||||
|
||||
I = true;
|
||||
resetLow = false;
|
||||
static if (_chip == "65C02") { D = false; }
|
||||
signals.resetLow = false;
|
||||
signals.updateSignals();
|
||||
|
||||
mixin(ReadWord(_PC, "RESET_VECTOR") ~
|
||||
Done());
|
||||
}
|
||||
|
||||
void doNMI()
|
||||
{
|
||||
mixin(Peek("PC") ~ Peek("PC") ~
|
||||
PushPC() ~
|
||||
Push("statusToByte() & ~0x10"));
|
||||
|
||||
I = true;
|
||||
static if (_chip == "65C02") { D = false; }
|
||||
signals.nmiLow = false;
|
||||
signals.updateSignals();
|
||||
|
||||
mixin(ReadWord(_PC, "NMI_VECTOR") ~
|
||||
Done());
|
||||
}
|
||||
|
||||
void doIRQ()
|
||||
{
|
||||
mixin(Peek("PC") ~ Peek("PC") ~
|
||||
PushPC() ~
|
||||
Push("statusToByte() & ~0x10"));
|
||||
|
||||
I = true;
|
||||
static if (_chip == "65C02") { D = false; }
|
||||
|
||||
mixin(ReadWord(_PC, "IRQ_VECTOR") ~
|
||||
Done());
|
||||
}
|
||||
|
||||
version(OpDelegates) mixin (OpMethods(_chip));
|
||||
}
|
||||
|
||||
|
||||
enum ushort IRQ_VECTOR = 0xFFFE;
|
||||
enum ushort RESET_VECTOR = 0xFFFC;
|
||||
enum ushort NMI_VECTOR = 0xFFFA;
|
||||
|
||||
|
||||
//alias Cpu!("6502", false, false) T1;
|
||||
|
|
|
@ -55,7 +55,8 @@ class System(string chip) : SystemBase
|
|||
|
||||
Cpu!(chip, AddressDecoder, Timer) cpu;
|
||||
// XXX
|
||||
bool* cpuRun, signalActive, resetLow;
|
||||
bool* cpuRun;
|
||||
Signals signals;
|
||||
|
||||
IOMem ioMem;
|
||||
Peripherals peripherals;
|
||||
|
@ -171,8 +172,7 @@ class System(string chip) : SystemBase
|
|||
cpu = new Cpu!(chip, AddressDecoder, Timer)(decoder, timer);
|
||||
// XXX
|
||||
cpuRun = &cpu.keepRunning;
|
||||
signalActive = &cpu.signalActive;
|
||||
resetLow = &cpu.resetLow;
|
||||
signals = cpu.signals;
|
||||
|
||||
debug(disassemble) cpu.memoryName = &decoder.memoryReadName;
|
||||
// timer.onPrimaryStop(&primaryStop);
|
||||
|
@ -212,8 +212,7 @@ class System(string chip) : SystemBase
|
|||
}
|
||||
|
||||
peripherals.reset();
|
||||
*signalActive = true;
|
||||
*resetLow = true;
|
||||
signals.triggerReset();
|
||||
}
|
||||
|
||||
override void execute()
|
||||
|
|
|
@ -971,7 +971,7 @@ auto setup_op_TSB()
|
|||
|
||||
|
||||
// For BRK.
|
||||
auto setup_op_BRK()
|
||||
auto setup_op_BRK(T)()
|
||||
{
|
||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||
string msg, TestSetup* next)
|
||||
|
@ -1953,7 +1953,7 @@ auto expect_TSB()
|
|||
}
|
||||
|
||||
// For BRK.
|
||||
auto expect_BRK()
|
||||
auto expect_BRK(T)()
|
||||
{
|
||||
void expect(ref Expected expected, const OpInfo info)
|
||||
{
|
||||
|
@ -1968,6 +1968,7 @@ auto expect_BRK()
|
|||
decSP(cpu);
|
||||
setPC(cpu, info.addr);
|
||||
setFlag(cpu, Flag.I);
|
||||
if (isCMOS!T) clearFlag(cpu, Flag.D);
|
||||
}
|
||||
}
|
||||
return &expect;
|
||||
|
@ -2523,7 +2524,7 @@ if (isCpu!T)
|
|||
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([0x00], "BRK!T");
|
||||
get_both([0x40], "RTI");
|
||||
get_both([0x60], "RTS");
|
||||
get_test([0x6C], "JMP_ind", "isCMOS!T");
|
||||
|
@ -2688,7 +2689,7 @@ auto report_debug()
|
|||
writeln(format(" | %s", formatMemory(h.b, 8)));
|
||||
}
|
||||
}
|
||||
if (badCpu || badMem) throw new Exception("BAD");
|
||||
if (badCpu || badMem) throw new TestException("func");
|
||||
}
|
||||
return &report;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ enum Tests
|
|||
Func = 1,
|
||||
Bus = 2,
|
||||
Dec = 4,
|
||||
All = 7
|
||||
Int = 8,
|
||||
All = 15
|
||||
}
|
||||
|
||||
string[OpDefs] defStrings;
|
||||
|
@ -28,7 +29,8 @@ static this()
|
|||
fNames = [
|
||||
Tests.Func:" test_func.d ",
|
||||
Tests.Bus:" test_bus.d ",
|
||||
Tests.Dec:" test_decimal.d "
|
||||
Tests.Dec:" test_decimal.d ",
|
||||
Tests.Int:" test_signal.d "
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -80,8 +82,8 @@ void main(string[] args)
|
|||
{
|
||||
writeln(
|
||||
`Options:
|
||||
--test=type Func, Bus, Dec, or All
|
||||
--def=style Delegates, Switch, or NestedSwitch
|
||||
--test=type Func, Bus, Dec, Int, or All
|
||||
--def=style Delegates, Switch, NestedSwitch, or All
|
||||
--op=num test opcode 'num' (num is hex)
|
||||
--op=name test all opcodes named 'name'
|
||||
--addr=mode test all opcodes with addressing mode 'mode'
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
import std.stdio, std.string, std.exception;
|
||||
|
||||
import test.d6502.base, test.d6502.cpu;
|
||||
|
||||
|
||||
auto setup_vectors(T)(bool res, bool nmi, bool irq)
|
||||
{
|
||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||
string msg, TestSetup* next)
|
||||
{
|
||||
mixin testCallNext;
|
||||
setPC(cpu, 0xfd00);
|
||||
auto ndata = [Block(0xfd00, [0xea, 0xea]),
|
||||
Block(0xfe35, [0xea, 0xea])];
|
||||
if (res)
|
||||
callNext("RESET ", ndata ~
|
||||
[Block(0xfffa, [0x00, 0xff, 0x35, 0xfe, 0x80, 0xff])]);
|
||||
else if (nmi)
|
||||
callNext("NMI ", ndata ~
|
||||
[Block(0xfffa, [0x35, 0xfe, 0x40, 0xff, 0x80, 0xff])]);
|
||||
else if (irq && !getFlag(cpu, Flag.I))
|
||||
callNext("IRQ ", ndata ~
|
||||
[Block(0xfffa, [0x00, 0xff, 0x40, 0xff, 0x35, 0xfe])]);
|
||||
else
|
||||
callNext("NOP ", ndata);
|
||||
}
|
||||
|
||||
return connect(setup_mask_flags(), setup_flag(Flag.I), TestSetup(&setup));
|
||||
}
|
||||
|
||||
|
||||
auto accesses_reset(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto pc = getPC(cpu);
|
||||
auto sp = getSP(cpu);
|
||||
auto sp1 = pageWrapAdd(sp, -1);
|
||||
auto sp2 = pageWrapAdd(sp, -2);
|
||||
|
||||
cycles = 7;
|
||||
return If!(strict)(
|
||||
[Bus(Action.READ, pc),
|
||||
Bus(Action.READ, pc),
|
||||
Bus(Action.READ, sp),
|
||||
Bus(Action.READ, sp1),
|
||||
Bus(Action.READ, sp2)]) ~
|
||||
[Bus(Action.READ, 0xfffc),
|
||||
Bus(Action.READ, 0xfffd)];
|
||||
}
|
||||
|
||||
auto accesses_nmi(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto pc = getPC(cpu);
|
||||
auto sp = getSP(cpu);
|
||||
auto sp1 = pageWrapAdd(sp, -1);
|
||||
auto sp2 = pageWrapAdd(sp, -2);
|
||||
|
||||
cycles = 7;
|
||||
return If!(strict)(
|
||||
[Bus(Action.READ, pc),
|
||||
Bus(Action.READ, pc)]) ~
|
||||
[Bus(Action.WRITE, sp),
|
||||
Bus(Action.WRITE, sp1),
|
||||
Bus(Action.WRITE, sp2),
|
||||
Bus(Action.READ, 0xfffa),
|
||||
Bus(Action.READ, 0xfffb)];
|
||||
}
|
||||
|
||||
auto accesses_irq(T)(T cpu, ref TestMemory mem, out int cycles)
|
||||
if (isCpu!T)
|
||||
{
|
||||
auto pc = getPC(cpu);
|
||||
auto sp = getSP(cpu);
|
||||
auto sp1 = pageWrapAdd(sp, -1);
|
||||
auto sp2 = pageWrapAdd(sp, -2);
|
||||
|
||||
cycles = 7;
|
||||
return If!(strict)(
|
||||
[Bus(Action.READ, pc),
|
||||
Bus(Action.READ, pc)]) ~
|
||||
[Bus(Action.WRITE, sp),
|
||||
Bus(Action.WRITE, sp1),
|
||||
Bus(Action.WRITE, sp2),
|
||||
Bus(Action.READ, 0xfffe),
|
||||
Bus(Action.READ, 0xffff)];
|
||||
}
|
||||
|
||||
|
||||
auto check_signal_timing(T)(bool res, bool nmi, bool irq, 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);
|
||||
|
||||
if (res) testcpu.signals.triggerReset();
|
||||
if (nmi) testcpu.signals.triggerNMI();
|
||||
if (irq) testcpu.signals.assertIRQ();
|
||||
|
||||
int expCycles, dummy;
|
||||
Bus[] expBus;
|
||||
auto accesses_after = [Bus(Action.READ, 0xfe35)] ~
|
||||
If!(strict)([Bus(Action.READ, 0xfe36)]);
|
||||
if (res)
|
||||
expBus = accesses_reset(testcpu, mem, expCycles) ~
|
||||
accesses_after;
|
||||
else if (nmi)
|
||||
expBus = accesses_nmi(testcpu, mem, expCycles) ~
|
||||
accesses_after;
|
||||
else if (irq && !getFlag(testcpu, Flag.I))
|
||||
expBus = accesses_irq(testcpu, mem, expCycles) ~
|
||||
accesses_after;
|
||||
else
|
||||
expBus = accesses_reg(testcpu, mem, dummy);
|
||||
expCycles += 2;
|
||||
expBus = expBus ~ new Bus[9 - expBus.length];
|
||||
|
||||
connectMem(testcpu, mem);
|
||||
auto actualBus = recordBus(testcpu, 9);
|
||||
auto actualCycles = recordCycles(testcpu);
|
||||
|
||||
runOneOpcode(testcpu);
|
||||
|
||||
report(actualCycles, actualBus, expCycles, expBus,
|
||||
opcode, T.stringof ~ " | " ~ msg);
|
||||
callNext();
|
||||
}
|
||||
return TestSetup(&setup);
|
||||
}
|
||||
|
||||
|
||||
auto expect_int(T)(bool res, bool nmi, bool irq)
|
||||
{
|
||||
void expect(ref Expected expected, const OpInfo info)
|
||||
{
|
||||
with(expected)
|
||||
{
|
||||
if (res || nmi || (irq && !getFlag(cpu, Flag.I)))
|
||||
{
|
||||
ushort ret = cast(ushort)(getPC(cpu));
|
||||
if (!res)
|
||||
mem[getSP(cpu)] = addrHi(ret);
|
||||
decSP(cpu);
|
||||
if (!res)
|
||||
mem[getSP(cpu)] = addrLo(ret);
|
||||
decSP(cpu);
|
||||
if (!res)
|
||||
mem[getSP(cpu)] = getStatus(cpu) & ~0x10;
|
||||
decSP(cpu);
|
||||
setPC(cpu, 0xfe36);
|
||||
setFlag(cpu, Flag.I);
|
||||
if (isCMOS!T) clearFlag(cpu, Flag.D);
|
||||
}
|
||||
else
|
||||
{
|
||||
setPC(cpu, 0xfd01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &expect;
|
||||
}
|
||||
|
||||
|
||||
auto check_signal(T)(bool res, bool nmi, bool irq, 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_int!T(res, nmi, irq)(expected, info);
|
||||
connectMem(testcpu, mem);
|
||||
|
||||
if (res) testcpu.signals.triggerReset();
|
||||
if (nmi) testcpu.signals.triggerNMI();
|
||||
if (irq) testcpu.signals.assertIRQ();
|
||||
|
||||
runOneOpcode(testcpu);
|
||||
auto cpuResult = CpuInfo.fromCpu(testcpu);
|
||||
report(opcode, expected, cpuResult, mem, T.stringof ~ " | " ~ msg);
|
||||
callNext();
|
||||
}
|
||||
return TestSetup(&setup);
|
||||
}
|
||||
|
||||
|
||||
void test_signals(T)()
|
||||
{
|
||||
void foo(T)(bool res, bool nmi, bool irq)
|
||||
{
|
||||
auto run = connect(setup_vectors!T(res, nmi, irq),
|
||||
check_signal_timing!T(res, nmi, irq, report_timing_debug()));
|
||||
run.run(0xea);
|
||||
}
|
||||
|
||||
void bar(T)(bool res, bool nmi, bool irq)
|
||||
{
|
||||
auto run = connect(setup_vectors!T(res, nmi, irq),
|
||||
check_signal!T(res, nmi, irq, report_debug()));
|
||||
run.run(0xea);
|
||||
}
|
||||
|
||||
foreach (res; [false, true])
|
||||
{
|
||||
foreach (nmi; [false, true])
|
||||
{
|
||||
foreach (irq; [false, true])
|
||||
{
|
||||
foo!T(res, nmi, irq);
|
||||
bar!T(res, nmi, irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_cli_delay(T)()
|
||||
{
|
||||
void run_test(ubyte opcode2, ushort delayPC, string name)
|
||||
{
|
||||
auto mem = TestMemory(Block(0xfd00, [0x58, opcode2, 0xea]),
|
||||
Block(0xfe35, [0xea, 0xea, 0xea]),
|
||||
Block(0xfffe, [0x35, 0xfe]));
|
||||
auto cpu = makeCpu!T();
|
||||
setPC(cpu, 0xfd00);
|
||||
setFlag(cpu, Flag.I);
|
||||
cpu.signals.assertIRQ();
|
||||
connectMem(cpu, mem);
|
||||
runOneOpcode(cpu);
|
||||
runOneOpcode(cpu);
|
||||
runOneOpcode(cpu);
|
||||
|
||||
static if (isCMOS!T) { auto expPC = 0xfe37; }
|
||||
else { auto expPC = delayPC; }
|
||||
|
||||
if (getPC(cpu) != expPC)
|
||||
{
|
||||
writeln(format(name ~ " expected pc $%0.4x got $%0.4x",
|
||||
expPC, getPC(cpu)));
|
||||
throw new TestException("cli_delay_1");
|
||||
}
|
||||
}
|
||||
|
||||
run_test(0xea, 0xfe36, "CLI-delay");
|
||||
run_test(0x78, 0xfd03, "CLI-delay-allows-SEI");
|
||||
run_test(0x58, 0xfe36, "multiple-CLI");
|
||||
}
|
||||
|
||||
void test_plp_delay(T)()
|
||||
{
|
||||
void run_test(ubyte opcode2, ushort delayPC, string name)
|
||||
{
|
||||
auto mem = TestMemory(Block(0x1fe, [0x00, 0x00]),
|
||||
Block(0xfd00, [0x28, opcode2, 0xea]),
|
||||
Block(0xfe35, [0xea, 0xea, 0xea]),
|
||||
Block(0xfffe, [0x35, 0xfe]));
|
||||
auto cpu = makeCpu!T();
|
||||
setPC(cpu, 0xfd00);
|
||||
setSP(cpu, 0x01fd);
|
||||
setFlag(cpu, Flag.I);
|
||||
cpu.signals.assertIRQ();
|
||||
connectMem(cpu, mem);
|
||||
runOneOpcode(cpu);
|
||||
runOneOpcode(cpu);
|
||||
runOneOpcode(cpu);
|
||||
|
||||
static if (isCMOS!T) { auto expPC = 0xfe37; }
|
||||
else { auto expPC = delayPC; }
|
||||
|
||||
if (getPC(cpu) != expPC)
|
||||
{
|
||||
writeln(format(name ~ " expected pc $%0.4x got $%0.4x",
|
||||
expPC, getPC(cpu)));
|
||||
throw new TestException("cli_delay_1");
|
||||
}
|
||||
}
|
||||
|
||||
run_test(0xea, 0xfe36, "PLP-delay");
|
||||
run_test(0x78, 0xfd03, "PLP-delay-allows-SEI");
|
||||
run_test(0x28, 0xfe36, "multiple-PLP");
|
||||
}
|
||||
|
||||
void test_sei_interruptable(T)()
|
||||
{
|
||||
auto mem = TestMemory(Block(0xfd00, [0x78, 0xea, 0xea]),
|
||||
Block(0xfe35, [0xea, 0xea, 0xea]),
|
||||
Block(0xfffe, [0x35, 0xfe]));
|
||||
auto cpu = makeCpu!T();
|
||||
setPC(cpu, 0xfd00);
|
||||
clearFlag(cpu, Flag.I);
|
||||
cpu.signals.assertIRQ();
|
||||
connectMem(cpu, mem);
|
||||
runOneOpcode(cpu);
|
||||
|
||||
if (getPC(cpu) != 0xfe36)
|
||||
{
|
||||
writeln(format("SEI expected pc $fe36 got $%0.4x", getPC(cpu)));
|
||||
throw new TestException("cli_delay_1");
|
||||
}
|
||||
}
|
||||
|
||||
void test_nmi_brk(T)()
|
||||
{
|
||||
class RunTest
|
||||
{
|
||||
uint ticks, nmiTick;
|
||||
T cpu;
|
||||
void tick()
|
||||
{
|
||||
if (ticks == nmiTick) cpu.signals.triggerNMI();
|
||||
ticks++;
|
||||
}
|
||||
this(uint nmiTick)
|
||||
{
|
||||
this.nmiTick = nmiTick;
|
||||
auto mem = TestMemory(Block(0xfd00, [0x00, 0xea]),
|
||||
Block(0xfe35, [0xea]),
|
||||
Block(0xff45, [0xea]),
|
||||
Block(0xfffa, [0x45, 0xff]),
|
||||
Block(0xfffe, [0x35, 0xfe]));
|
||||
cpu = makeCpu!T();
|
||||
setPC(cpu, 0xfd00);
|
||||
connectMem(cpu, mem);
|
||||
cpu.clock.dtick = &tick;
|
||||
runOneOpcode(cpu);
|
||||
ushort expPC;
|
||||
if (nmiTick < 5 && isNMOS!T)
|
||||
expPC = 0xff45;
|
||||
else
|
||||
expPC = 0xfe35;
|
||||
if (getPC(cpu) != expPC)
|
||||
{
|
||||
writeln(format("nmi-brk tick %d expected $%0.4x got $%0.4x",
|
||||
nmiTick, expPC, getPC(cpu)));
|
||||
throw new TestException("nmi-brk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (t; 0..6)
|
||||
{
|
||||
auto dummy = new RunTest(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
alias CPU!("6502") T1;
|
||||
writeln("Testing signals, 6502");
|
||||
test_signals!T1();
|
||||
test_cli_delay!T1();
|
||||
test_plp_delay!T1();
|
||||
test_sei_interruptable!T1();
|
||||
version(Cumulative) {}
|
||||
else { test_nmi_brk!T1(); }
|
||||
|
||||
alias CPU!("65C02") T2;
|
||||
writeln("Testing signals, 65C02");
|
||||
test_signals!T2();
|
||||
test_cli_delay!T2();
|
||||
test_plp_delay!T2();
|
||||
test_sei_interruptable!T2();
|
||||
version(Cumulative) {}
|
||||
else { test_nmi_brk!T2(); }
|
||||
}
|
Loading…
Reference in New Issue