Interrupt handling

This commit is contained in:
edmccard 2012-04-25 16:50:03 -04:00
parent 6ac568742e
commit c663b46003
6 changed files with 541 additions and 36 deletions

View File

@ -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";

View File

@ -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;

View File

@ -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()

View File

@ -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;
}

View File

@ -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'

371
test/d6502/test_signal.d Normal file
View File

@ -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(); }
}