diff --git a/src/cpu/ctfe_d6502.d b/src/cpu/ctfe_d6502.d index e9eeb19..0523ca1 100644 --- a/src/cpu/ctfe_d6502.d +++ b/src/cpu/ctfe_d6502.d @@ -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"; diff --git a/src/cpu/d6502.d b/src/cpu/d6502.d index 5cc5e2e..50b3044 100644 --- a/src/cpu/d6502.d +++ b/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; diff --git a/src/system/base.d b/src/system/base.d index 2b4543a..a630c26 100644 --- a/src/system/base.d +++ b/src/system/base.d @@ -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() diff --git a/test/d6502/base.d b/test/d6502/base.d index 70b531b..db8fb1f 100644 --- a/test/d6502/base.d +++ b/test/d6502/base.d @@ -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; } diff --git a/test/d6502/runtests.d b/test/d6502/runtests.d index 90ad4e7..21bec6e 100644 --- a/test/d6502/runtests.d +++ b/test/d6502/runtests.d @@ -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' diff --git a/test/d6502/test_signal.d b/test/d6502/test_signal.d new file mode 100644 index 0000000..4e4c2ee --- /dev/null +++ b/test/d6502/test_signal.d @@ -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(); } +}