Interrupt handling
This commit is contained in:
parent
6ac568742e
commit
c663b46003
|
@ -73,7 +73,7 @@ string OpMethods(string chip)
|
||||||
foreach (op; 0..256)
|
foreach (op; 0..256)
|
||||||
{
|
{
|
||||||
ret ~= "final void opcode_" ~ Hex2(op) ~ "()\n{\n" ~
|
ret ~= "final void opcode_" ~ Hex2(op) ~ "()\n{\n" ~
|
||||||
If!(cumulative)("int cycles = 1;\n") ~
|
// If!(cumulative)("int cycles = 1;\n") ~
|
||||||
OpBody(op, chip) ~ "}\n";
|
OpBody(op, chip) ~ "}\n";
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -141,7 +141,7 @@ string OpBody(int op, string chip)
|
||||||
final switch (opName(op, chip))
|
final switch (opName(op, chip))
|
||||||
{
|
{
|
||||||
case "BRK":
|
case "BRK":
|
||||||
ret ~= Break();
|
ret ~= Break(chip);
|
||||||
break;
|
break;
|
||||||
case "RTI":
|
case "RTI":
|
||||||
ret ~= RetInt();
|
ret ~= RetInt();
|
||||||
|
@ -192,7 +192,7 @@ string OpBody(int op, string chip)
|
||||||
ret ~= SetFlag(_C);
|
ret ~= SetFlag(_C);
|
||||||
break;
|
break;
|
||||||
case "CLI":
|
case "CLI":
|
||||||
ret ~= ClearFlag(_I);
|
ret ~= ClearInt(chip);
|
||||||
break;
|
break;
|
||||||
case "SEI":
|
case "SEI":
|
||||||
ret ~= SetFlag(_I);
|
ret ~= SetFlag(_I);
|
||||||
|
@ -240,10 +240,10 @@ string OpBody(int op, string chip)
|
||||||
ret ~= Inc(_Y);
|
ret ~= Inc(_Y);
|
||||||
break;
|
break;
|
||||||
case "PHP":
|
case "PHP":
|
||||||
ret ~= Push(Attr("statusToByte()"));
|
ret ~= Push("statusToByte()");
|
||||||
break;
|
break;
|
||||||
case "PLP":
|
case "PLP":
|
||||||
ret ~= PullStatus();
|
ret ~= PullSReg(chip);
|
||||||
break;
|
break;
|
||||||
case "PLA":
|
case "PLA":
|
||||||
ret ~= PullReg(_A);
|
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() ~
|
return IncPC() ~
|
||||||
PushPC() ~
|
PushPC() ~
|
||||||
Push(Attr("statusToByte()")) ~
|
Push("statusToByte()") ~
|
||||||
SetFlag(_I) ~
|
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");
|
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)
|
string ReadInto(string var, string action, string addr)
|
||||||
{
|
{
|
||||||
|
@ -1114,13 +1138,21 @@ string DecSP()
|
||||||
return "--" ~ _S ~ ";\n";
|
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()
|
string PullStatus()
|
||||||
{
|
{
|
||||||
return Peek(STACK) ~
|
return Peek(STACK) ~
|
||||||
IncSP() ~
|
IncSP() ~
|
||||||
Tick() ~
|
Tick() ~
|
||||||
Attr("statusFromByte") ~ "(" ~
|
"statusFromByte(" ~ ReadRaw(STACK) ~ ");\n";
|
||||||
ReadRaw(STACK) ~ ");\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string PullInto(string var)
|
string PullInto(string var)
|
||||||
|
@ -1171,6 +1203,14 @@ string ClearFlag(string flag)
|
||||||
return flag ~ " = false;\n";
|
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)
|
string UpdateFlag(string flag, string val)
|
||||||
{
|
{
|
||||||
return flag ~ " = (" ~ val ~ ");\n";
|
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)
|
final class Cpu(string chip, MEM, CLK)
|
||||||
if (__traits(compiles, {
|
if (__traits(compiles, {
|
||||||
MEM m; ubyte val; ushort addr;
|
MEM m; ubyte val; ushort addr;
|
||||||
|
@ -64,10 +102,20 @@ if (__traits(compiles, {
|
||||||
ubyte N, Z;
|
ubyte N, Z;
|
||||||
bool V, D, I, C;
|
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)
|
version(OpDelegates)
|
||||||
{
|
{
|
||||||
mixin(OpArrayDef());
|
mixin(OpArrayDef());
|
||||||
version(Cumulative) { int cycles; }
|
|
||||||
ushort address, base;
|
ushort address, base;
|
||||||
ubyte data;
|
ubyte data;
|
||||||
}
|
}
|
||||||
|
@ -76,6 +124,7 @@ if (__traits(compiles, {
|
||||||
{
|
{
|
||||||
this.memory = memory;
|
this.memory = memory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
|
signals = new Signals();
|
||||||
|
|
||||||
version(OpDelegates) mixin(OpArrayInit());
|
version(OpDelegates) mixin(OpArrayInit());
|
||||||
}
|
}
|
||||||
|
@ -101,25 +150,22 @@ if (__traits(compiles, {
|
||||||
(N & 0x80);
|
(N & 0x80);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keepRunning;
|
|
||||||
bool signalActive;
|
|
||||||
bool resetLow;
|
|
||||||
|
|
||||||
final void run(bool continuous)
|
final void run(bool continuous)
|
||||||
{
|
{
|
||||||
keepRunning = continuous;
|
keepRunning = continuous;
|
||||||
ubyte opcode;
|
ubyte opcode;
|
||||||
static if (!opArray)
|
static if (!opArray)
|
||||||
{
|
{
|
||||||
version(Cumulative) { int cycles; }
|
|
||||||
ushort address, base;
|
ushort address, base;
|
||||||
ubyte data;
|
ubyte data;
|
||||||
}
|
}
|
||||||
do
|
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(); }
|
else { clock.tick(); }
|
||||||
if (signalActive) handleSignals();
|
|
||||||
opcode = memory.read(PC++);
|
opcode = memory.read(PC++);
|
||||||
mixin(OpExecute(_chip));
|
mixin(OpExecute(_chip));
|
||||||
} while (keepRunning);
|
} while (keepRunning);
|
||||||
|
@ -128,31 +174,77 @@ if (__traits(compiles, {
|
||||||
// TODO: irq/nmi
|
// TODO: irq/nmi
|
||||||
void handleSignals()
|
void handleSignals()
|
||||||
{
|
{
|
||||||
if (resetLow) doReset();
|
if (signals.resetLow) { doReset(); return; }
|
||||||
// XXX fix when more than one signal
|
|
||||||
signalActive = resetLow;
|
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()
|
void doReset()
|
||||||
{
|
{
|
||||||
mixin(Tick() ~ Tick() ~
|
mixin(Peek("PC") ~ Peek("PC") ~
|
||||||
Peek(STACK) ~ DecSP() ~
|
Peek(STACK) ~ DecSP() ~
|
||||||
Peek(STACK) ~ DecSP() ~
|
Peek(STACK) ~ DecSP() ~
|
||||||
Peek(STACK) ~ DecSP());
|
Peek(STACK) ~ DecSP());
|
||||||
|
|
||||||
I = true;
|
I = true;
|
||||||
resetLow = false;
|
static if (_chip == "65C02") { D = false; }
|
||||||
|
signals.resetLow = false;
|
||||||
|
signals.updateSignals();
|
||||||
|
|
||||||
mixin(ReadWord(_PC, "RESET_VECTOR") ~
|
mixin(ReadWord(_PC, "RESET_VECTOR") ~
|
||||||
Done());
|
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));
|
version(OpDelegates) mixin (OpMethods(_chip));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum ushort IRQ_VECTOR = 0xFFFE;
|
enum ushort IRQ_VECTOR = 0xFFFE;
|
||||||
enum ushort RESET_VECTOR = 0xFFFC;
|
enum ushort RESET_VECTOR = 0xFFFC;
|
||||||
|
enum ushort NMI_VECTOR = 0xFFFA;
|
||||||
|
|
||||||
|
|
||||||
//alias Cpu!("6502", false, false) T1;
|
//alias Cpu!("6502", false, false) T1;
|
||||||
|
|
|
@ -55,7 +55,8 @@ class System(string chip) : SystemBase
|
||||||
|
|
||||||
Cpu!(chip, AddressDecoder, Timer) cpu;
|
Cpu!(chip, AddressDecoder, Timer) cpu;
|
||||||
// XXX
|
// XXX
|
||||||
bool* cpuRun, signalActive, resetLow;
|
bool* cpuRun;
|
||||||
|
Signals signals;
|
||||||
|
|
||||||
IOMem ioMem;
|
IOMem ioMem;
|
||||||
Peripherals peripherals;
|
Peripherals peripherals;
|
||||||
|
@ -171,8 +172,7 @@ class System(string chip) : SystemBase
|
||||||
cpu = new Cpu!(chip, AddressDecoder, Timer)(decoder, timer);
|
cpu = new Cpu!(chip, AddressDecoder, Timer)(decoder, timer);
|
||||||
// XXX
|
// XXX
|
||||||
cpuRun = &cpu.keepRunning;
|
cpuRun = &cpu.keepRunning;
|
||||||
signalActive = &cpu.signalActive;
|
signals = cpu.signals;
|
||||||
resetLow = &cpu.resetLow;
|
|
||||||
|
|
||||||
debug(disassemble) cpu.memoryName = &decoder.memoryReadName;
|
debug(disassemble) cpu.memoryName = &decoder.memoryReadName;
|
||||||
// timer.onPrimaryStop(&primaryStop);
|
// timer.onPrimaryStop(&primaryStop);
|
||||||
|
@ -212,8 +212,7 @@ class System(string chip) : SystemBase
|
||||||
}
|
}
|
||||||
|
|
||||||
peripherals.reset();
|
peripherals.reset();
|
||||||
*signalActive = true;
|
signals.triggerReset();
|
||||||
*resetLow = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override void execute()
|
override void execute()
|
||||||
|
|
|
@ -971,7 +971,7 @@ auto setup_op_TSB()
|
||||||
|
|
||||||
|
|
||||||
// For BRK.
|
// For BRK.
|
||||||
auto setup_op_BRK()
|
auto setup_op_BRK(T)()
|
||||||
{
|
{
|
||||||
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
auto setup(ubyte opcode, CpuInfo cpu, Block[] data, OpInfo info,
|
||||||
string msg, TestSetup* next)
|
string msg, TestSetup* next)
|
||||||
|
@ -1953,7 +1953,7 @@ auto expect_TSB()
|
||||||
}
|
}
|
||||||
|
|
||||||
// For BRK.
|
// For BRK.
|
||||||
auto expect_BRK()
|
auto expect_BRK(T)()
|
||||||
{
|
{
|
||||||
void expect(ref Expected expected, const OpInfo info)
|
void expect(ref Expected expected, const OpInfo info)
|
||||||
{
|
{
|
||||||
|
@ -1968,6 +1968,7 @@ auto expect_BRK()
|
||||||
decSP(cpu);
|
decSP(cpu);
|
||||||
setPC(cpu, info.addr);
|
setPC(cpu, info.addr);
|
||||||
setFlag(cpu, Flag.I);
|
setFlag(cpu, Flag.I);
|
||||||
|
if (isCMOS!T) clearFlag(cpu, Flag.D);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &expect;
|
return &expect;
|
||||||
|
@ -2523,7 +2524,7 @@ if (isCpu!T)
|
||||||
get_both([0x84, 0x8C, 0x94], "store", "Reg.Y");
|
get_both([0x84, 0x8C, 0x94], "store", "Reg.Y");
|
||||||
get_expect(BRANCH_OPS!T, "branch");
|
get_expect(BRANCH_OPS!T, "branch");
|
||||||
get_both([0x24, 0x2C], "BIT");
|
get_both([0x24, 0x2C], "BIT");
|
||||||
get_both([0x00], "BRK");
|
get_both([0x00], "BRK!T");
|
||||||
get_both([0x40], "RTI");
|
get_both([0x40], "RTI");
|
||||||
get_both([0x60], "RTS");
|
get_both([0x60], "RTS");
|
||||||
get_test([0x6C], "JMP_ind", "isCMOS!T");
|
get_test([0x6C], "JMP_ind", "isCMOS!T");
|
||||||
|
@ -2688,7 +2689,7 @@ auto report_debug()
|
||||||
writeln(format(" | %s", formatMemory(h.b, 8)));
|
writeln(format(" | %s", formatMemory(h.b, 8)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (badCpu || badMem) throw new Exception("BAD");
|
if (badCpu || badMem) throw new TestException("func");
|
||||||
}
|
}
|
||||||
return &report;
|
return &report;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ enum Tests
|
||||||
Func = 1,
|
Func = 1,
|
||||||
Bus = 2,
|
Bus = 2,
|
||||||
Dec = 4,
|
Dec = 4,
|
||||||
All = 7
|
Int = 8,
|
||||||
|
All = 15
|
||||||
}
|
}
|
||||||
|
|
||||||
string[OpDefs] defStrings;
|
string[OpDefs] defStrings;
|
||||||
|
@ -28,7 +29,8 @@ static this()
|
||||||
fNames = [
|
fNames = [
|
||||||
Tests.Func:" test_func.d ",
|
Tests.Func:" test_func.d ",
|
||||||
Tests.Bus:" test_bus.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(
|
writeln(
|
||||||
`Options:
|
`Options:
|
||||||
--test=type Func, Bus, Dec, or All
|
--test=type Func, Bus, Dec, Int, or All
|
||||||
--def=style Delegates, Switch, or NestedSwitch
|
--def=style Delegates, Switch, NestedSwitch, or All
|
||||||
--op=num test opcode 'num' (num is hex)
|
--op=num test opcode 'num' (num is hex)
|
||||||
--op=name test all opcodes named 'name'
|
--op=name test all opcodes named 'name'
|
||||||
--addr=mode test all opcodes with addressing mode 'mode'
|
--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