diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.h b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.h index f1d0ec6a3..592b1bca3 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.h +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.h @@ -64,5 +64,6 @@ typedef NS_ENUM(NSInteger, CSTestMachineZ80Register) { @property(nonatomic) BOOL nmiLine; @property(nonatomic) BOOL irqLine; +@property(nonatomic) BOOL waitLine; @end diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm index 0a127c159..f7f8999ed 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm @@ -167,6 +167,11 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) { _processor->set_interrupt_line(irqLine ? true : false); } +- (void)setWaitLine:(BOOL)waitLine { + _waitLine = waitLine; + _processor->set_wait_line(waitLine ? true : false); +} + - (CPU::AllRAMProcessor *)processor { return _processor; } diff --git a/OSBindings/Mac/Clock SignalTests/Z80InterruptTests.swift b/OSBindings/Mac/Clock SignalTests/Z80InterruptTests.swift index ab536791f..5c25b9028 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80InterruptTests.swift +++ b/OSBindings/Mac/Clock SignalTests/Z80InterruptTests.swift @@ -10,6 +10,15 @@ import XCTest class Z80InterruptTests: XCTestCase { + private func assertNMI(machine: CSTestMachineZ80) { + // confirm that the PC is now at 0x66, that the old is on the stack and + // that IFF1 has migrated to IFF2 + XCTAssertEqual(machine.value(for: .programCounter), 0x66) + XCTAssertEqual(machine.value(atAddress: 0xffff), 0x01) + XCTAssertEqual(machine.value(atAddress: 0xfffe), 0x02) + XCTAssertEqual(machine.value(for: .IFF2), 0) + } + func testNMI() { let machine = CSTestMachineZ80() @@ -34,12 +43,45 @@ class Z80InterruptTests: XCTestCase { // run for eleven more cycles to allow the NMI to begin machine.runForNumber(ofCycles: 11) - // confirm that the PC is now at 0x66, that the old is on the stack and - // that IFF1 has migrated to IFF2 - XCTAssertEqual(machine.value(for: .programCounter), 0x66) - XCTAssertEqual(machine.value(atAddress: 0xffff), 0x01) - XCTAssertEqual(machine.value(atAddress: 0xfffe), 0x02) - XCTAssertEqual(machine.value(for: .IFF2), 0) + assertNMI(machine: machine) + } + + func testHaltNMIWait() { + let machine = CSTestMachineZ80() + + // start the PC at 0x0100 and install a NOP and a HALT for it + machine.setValue(0x0100, for: .programCounter) + machine.setValue(0, for: .IFF1) + machine.setValue(1, for: .IFF2) + machine.setValue(0x00, atAddress: 0x0100) + machine.setValue(0x76, atAddress: 0x0101) + + // put the stack at the top of memory + machine.setValue(0, for: .stackPointer) + + // run for ten cycles, check that the processor is halted and assert an NMI + machine.runForNumber(ofCycles: 10) + XCTAssert(machine.isHalted, "Machine should be halted") + machine.nmiLine = true + + // check that the machine ceases believing itsef to be halted after two cycles + machine.runForNumber(ofCycles: 1) + XCTAssert(machine.isHalted, "Machine should still be halted") + machine.runForNumber(ofCycles: 1) + XCTAssert(!machine.isHalted, "Machine should no longer be halted") + + // assert wait + machine.waitLine = true + + // run for twenty cycles, an arbitrary big number + machine.runForNumber(ofCycles: 20) + + // release wait + machine.waitLine = false + + // NMI should have run for two cycles, then waited, so now there should be nine cycles left + machine.runForNumber(ofCycles: 9) + assertNMI(machine: machine) } func testIRQDisabled() { diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index ce9c9bb46..5231f67a6 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -803,12 +803,15 @@ template class Processor { assemble_fetch_decode_execute(ddcb_page_, 3); MicroOp reset_program[] = Sequence(InternalOperation(3), {MicroOp::Reset}); + + // Justification for NMI timing: per Wilf Rigter on the ZX81 (http://www.user.dccnet.com/wrigter/index_files/ZX81WAIT.htm), + // wait cycles occur between T2 and T3 during NMI; extending the refresh cycle is also consistent with my guess + // for the action of other non-four-cycle opcode fetches MicroOp nmi_program[] = { { MicroOp::BeginNMI }, BusOp(ReadOpcodeStart()), - BusOp(ReadOpcodeWait(1, false)), BusOp(ReadOpcodeWait(1, true)), - BusOp(Refresh(2)), + BusOp(Refresh(3)), Push(pc_), { MicroOp::JumpTo66, nullptr, nullptr}, { MicroOp::MoveToNextProgram } diff --git a/Processors/Z80/Z80AllRAM.cpp b/Processors/Z80/Z80AllRAM.cpp index 49292aa72..ba5084719 100644 --- a/Processors/Z80/Z80AllRAM.cpp +++ b/Processors/Z80/Z80AllRAM.cpp @@ -90,6 +90,10 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor::set_non_maskable_interrupt_line(value); } + + void set_wait_line(bool value) { + CPU::Z80::Processor::set_wait_line(value); + } }; } diff --git a/Processors/Z80/Z80AllRAM.hpp b/Processors/Z80/Z80AllRAM.hpp index 9ec9726da..69b1c874f 100644 --- a/Processors/Z80/Z80AllRAM.hpp +++ b/Processors/Z80/Z80AllRAM.hpp @@ -33,8 +33,10 @@ class AllRAMProcessor: virtual void set_value_of_register(Register r, uint16_t value) = 0; virtual bool get_halt_line() = 0; virtual void reset_power_on() = 0; + virtual void set_interrupt_line(bool value) = 0; virtual void set_non_maskable_interrupt_line(bool value) = 0; + virtual void set_wait_line(bool value) = 0; protected: MemoryAccessDelegate *delegate_;