kosarev / z80

Fast and flexible Z80/i8080 emulator with C++ and Python APIs
MIT License
63 stars 10 forks source link

Complete the support for NMOS/CMOS Z80 differences #35

Open kosarev opened 3 years ago

kosarev commented 3 years ago

https://sinclair.wiki.zxnet.co.uk/wiki/Z80#Differences_between_NMOS_and_CMOS_Z80s says:

LD A,I and LD A,R bug The NMOS Z80s suffer a problem whereby LD A,I and LD A,R record the state of IFF2 after it has been reset if an interrupt is delivered during that instruction. This behaviour, along with workarounds for this for use in interrupt handlers are documented in the Z80 Family Questions and Answers section of the Zilog Product Specifications Databook, and is useful for detecting the model of Z80 in use, so as to determine whether the CPU (assuming it is a genuine NMOS or CMOS Z80) provides an 'OUT (C),0' instruction (NMOS), or 'OUT (C),255' instead (CMOS). http://z80.info/zip/ZilogProductSpecsDatabook129-143.pdf (OCRed version: https://archive.org/stream/Zilog-Z80familyDataBook1989OCR/Zilog-Z80familyDataBook1989OCR_djvu.txt)

From the Q&A clause mentioned:

Q: I don't seem to get the correct state of the interrupts when using the LD A, I and LD A, R instructions to read the state of IFF2. Why is this? How can I get around this? A: On CMOS Z80 CPU, we've fixed this problem. On NMOS Z80 CPU, in certain narrowly defined circumstances, theZ80CPU interrupt enable latch, IFF2, does not necessarily reflect the true interrupt status. The two instructions LD A, R and LD A, I copy the state of interrupt enable latch (IFF2) into the parity flag and modifies the accumulator contents (See table 7.0.1 in the Z80 CPU technical manual for details). Thus, it is possible to determine whether interrupts are enabled or disabled at the time that the instruction is executed. This facility is necessary to save the complete state of the machine. However, if an interrupt is accepted by the CPU during the execution of the instruction -- implying that the interrupts must be enabled -- the P/V flag is cleared. This incorrectly asserts that interrupts were disabled at the time the instruction was executed.

Related to #27.

simonowen commented 3 years ago

I wasn't aware of the difference in LD A,I / LD A,R behaviour in that case. I'll have to see if I can write a program to detect it, though the always-on RAM contention in the SAM Coupé makes it difficult to do single-cycle adjustments for this particular test.

Perhaps it could be implemented using a new handler that returns whether an interrupt is due in the next N cycles? Those instructions could then check ahead to see if they should change the result in the NMOS case. We need to know exactly when IFF2 is sampled within the instruction though.

kosarev commented 3 years ago

I understand we currently implement the CMOS behaviour. For NMOS, yes, we may need to change the way we initiate interrupts by letting the emulator know when ~INT is going to become active.

We need to know exactly when IFF2 is sampled within the instruction though.

If I remember it correctly that ~INT is being sampled during the last tick of instruction, then this suggests that we should get its value as it is at that last tick?