mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-28 22:30:30 +00:00
Merge pull request #137 from TomHarte/NMIWaitTest
Introduces an NMI/wait interrupt timing test
This commit is contained in:
commit
3dfe45d225
@ -64,5 +64,6 @@ typedef NS_ENUM(NSInteger, CSTestMachineZ80Register) {
|
|||||||
|
|
||||||
@property(nonatomic) BOOL nmiLine;
|
@property(nonatomic) BOOL nmiLine;
|
||||||
@property(nonatomic) BOOL irqLine;
|
@property(nonatomic) BOOL irqLine;
|
||||||
|
@property(nonatomic) BOOL waitLine;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -167,6 +167,11 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
|||||||
_processor->set_interrupt_line(irqLine ? true : false);
|
_processor->set_interrupt_line(irqLine ? true : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setWaitLine:(BOOL)waitLine {
|
||||||
|
_waitLine = waitLine;
|
||||||
|
_processor->set_wait_line(waitLine ? true : false);
|
||||||
|
}
|
||||||
|
|
||||||
- (CPU::AllRAMProcessor *)processor {
|
- (CPU::AllRAMProcessor *)processor {
|
||||||
return _processor;
|
return _processor;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,15 @@ import XCTest
|
|||||||
|
|
||||||
class Z80InterruptTests: XCTestCase {
|
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() {
|
func testNMI() {
|
||||||
let machine = CSTestMachineZ80()
|
let machine = CSTestMachineZ80()
|
||||||
|
|
||||||
@ -34,12 +43,45 @@ class Z80InterruptTests: XCTestCase {
|
|||||||
// run for eleven more cycles to allow the NMI to begin
|
// run for eleven more cycles to allow the NMI to begin
|
||||||
machine.runForNumber(ofCycles: 11)
|
machine.runForNumber(ofCycles: 11)
|
||||||
|
|
||||||
// confirm that the PC is now at 0x66, that the old is on the stack and
|
assertNMI(machine: machine)
|
||||||
// that IFF1 has migrated to IFF2
|
}
|
||||||
XCTAssertEqual(machine.value(for: .programCounter), 0x66)
|
|
||||||
XCTAssertEqual(machine.value(atAddress: 0xffff), 0x01)
|
func testHaltNMIWait() {
|
||||||
XCTAssertEqual(machine.value(atAddress: 0xfffe), 0x02)
|
let machine = CSTestMachineZ80()
|
||||||
XCTAssertEqual(machine.value(for: .IFF2), 0)
|
|
||||||
|
// 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() {
|
func testIRQDisabled() {
|
||||||
|
@ -803,12 +803,15 @@ template <class T> class Processor {
|
|||||||
assemble_fetch_decode_execute(ddcb_page_, 3);
|
assemble_fetch_decode_execute(ddcb_page_, 3);
|
||||||
|
|
||||||
MicroOp reset_program[] = Sequence(InternalOperation(3), {MicroOp::Reset});
|
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 nmi_program[] = {
|
||||||
{ MicroOp::BeginNMI },
|
{ MicroOp::BeginNMI },
|
||||||
BusOp(ReadOpcodeStart()),
|
BusOp(ReadOpcodeStart()),
|
||||||
BusOp(ReadOpcodeWait(1, false)),
|
|
||||||
BusOp(ReadOpcodeWait(1, true)),
|
BusOp(ReadOpcodeWait(1, true)),
|
||||||
BusOp(Refresh(2)),
|
BusOp(Refresh(3)),
|
||||||
Push(pc_),
|
Push(pc_),
|
||||||
{ MicroOp::JumpTo66, nullptr, nullptr},
|
{ MicroOp::JumpTo66, nullptr, nullptr},
|
||||||
{ MicroOp::MoveToNextProgram }
|
{ MicroOp::MoveToNextProgram }
|
||||||
|
@ -90,6 +90,10 @@ class ConcreteAllRAMProcessor: public AllRAMProcessor, public Processor<Concrete
|
|||||||
void set_non_maskable_interrupt_line(bool value) {
|
void set_non_maskable_interrupt_line(bool value) {
|
||||||
CPU::Z80::Processor<ConcreteAllRAMProcessor>::set_non_maskable_interrupt_line(value);
|
CPU::Z80::Processor<ConcreteAllRAMProcessor>::set_non_maskable_interrupt_line(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_wait_line(bool value) {
|
||||||
|
CPU::Z80::Processor<ConcreteAllRAMProcessor>::set_wait_line(value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,10 @@ class AllRAMProcessor:
|
|||||||
virtual void set_value_of_register(Register r, uint16_t value) = 0;
|
virtual void set_value_of_register(Register r, uint16_t value) = 0;
|
||||||
virtual bool get_halt_line() = 0;
|
virtual bool get_halt_line() = 0;
|
||||||
virtual void reset_power_on() = 0;
|
virtual void reset_power_on() = 0;
|
||||||
|
|
||||||
virtual void set_interrupt_line(bool value) = 0;
|
virtual void set_interrupt_line(bool value) = 0;
|
||||||
virtual void set_non_maskable_interrupt_line(bool value) = 0;
|
virtual void set_non_maskable_interrupt_line(bool value) = 0;
|
||||||
|
virtual void set_wait_line(bool value) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MemoryAccessDelegate *delegate_;
|
MemoryAccessDelegate *delegate_;
|
||||||
|
Loading…
Reference in New Issue
Block a user