Skip to content

Commit

Permalink
Correct the HALT and LDxR/CPxR/INxR/OTxR instructions behaviour.
Browse files Browse the repository at this point in the history
  • Loading branch information
jsanchezv committed Jan 4, 2022
1 parent cf66471 commit 2a685f7
Showing 1 changed file with 169 additions and 39 deletions.
208 changes: 169 additions & 39 deletions src/main/java/z80core/Z80.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,89 @@
* 29/01/2018 CB es el único prefijo de instrucción cuyo byte siguiente produce SIEMPRE un
* código de instrucción válido, de modo que no merece la pena dividirlo en dos y es mejor
* que se ejecute como una unidad indivisible.
*
* 03/01/2022 Se corrige el comportamiento de HALT, según esto:
*
* The "halt" instruction enables the HALT state after PC is incremented during the opcode
* fetch. The CPU neither decrements nor avoids incrementing PC "so that the instruction is
* re-executed" as Sean Young writes in section 5.4 of "The Undocumented Z80 Documented".
* During the HALT state, the CPU repeatedly executes an internal NOP operation. Each NOP
* consists of 1 M1 cycle with 4 T-states that fetches and disregards the next opcode after
* "halt" without incrementing PC. This opcode is read again and again until an exit
* condition occurs (i.e., INT, NMI or RESET). This was first documented by Tony Brewer and
* later re-confirmed by the HALT2INT test written by Mark Woodmass.
*
* If the opcode following halt is read from an address that is not contended, but the halt
* opcode is, or viceversa, then the contention emulation will not be accurate if the
* emulator doesn't implement this correctly. The test places a "halt" opcode in the
* boundaries of the VRAM so that this can be tested.
* ----------------------------------
* Contended RAM | Uncontended RAM
* --------------+-------------------
* ... | halt | nop | nop | ...
* --------------+-------------------
* 7FFE | 7FFF | 8000 | 8001 | ...
*
* Correct implementation:
*
* fetch(7FFFh)
* -> CPU increments PC
* -> CPU enables the HALT state
* (void)fetch(8000h)
* (void)fetch(8000h)
* (void)fetch(8000h)
* (void)fetch(8000h)
* (void)fetch(8000h)
* ...
* INT
* -> CPU disables the HALT state
* -> CPU responds to the interrupt
* -> CPU returns from the ISR
* fetch(8000h)
* fetch(8001h)
*
*
* Incorrect implementation:
*
* fetch(7FFFh)
* -> CPU enables the HALT state
* fetch(7FFFh)
* fetch(7FFFh)
* fetch(7FFFh)
* fetch(7FFFh)
* fetch(7FFFh)
* ...
* INT
* -> CPU disables the HALT state
* -> CPU responds to the interrupt
* -> CPU returns from the ISR
* fetch(8000h)
* fetch(8001h)
*
* https://spectrumcomputing.co.uk/forums/viewtopic.php?f=23&t=6058&start=20
*
* Se debe comprobar con HALT2INT (48k) y con Super HALT Invaders Test (128k)
* Si no está bien implementado, el Super Halt Invaders no incrementa la puntuación.
* Thanks to Woody & ZjoyKiLer
*
* 04/01/2022 Corregido comportamiento de las instrucciones LDxR/CPxR/INxR/OTxR cuando,
* en algún momento durante la repetición, se produce una interrupción. En ese caso,
* los flags que ve el manejador de la interrupción están modificados respecto al caso
* normal.
* https://spectrumcomputing.co.uk/forums/viewtopic.php?f=23&t=6102
* Comprobado con el test:
* "Z80 Block Flags Test v4.0 (2022-01-01)(Helcmanovsky, Peter)[!].tap"
*/
package z80core;

import jdk.internal.vm.annotation.Contended;
import lombok.extern.slf4j.Slf4j;
import machine.Clock;
import snapshots.Z80State;

import java.util.Arrays;

@Slf4j
public class Z80 {

private final Clock clock;
Expand Down Expand Up @@ -1716,10 +1790,7 @@ private void interruption() {
// int tmp = tEstados; // peek8 modifica los tEstados
lastFlagQ = false;
// Si estaba en un HALT esperando una INT, lo saca de la espera
if (halted) {
halted = false;
regPC = (regPC + 1) & 0xffff;
}
halted = false;

MemIoImpl.interruptHandlingTime(7);

Expand All @@ -1743,15 +1814,13 @@ private void interruption() {
*/
private void nmi() {
lastFlagQ = false;
halted = false;
// Esta lectura consigue dos cosas:
// 1.- La lectura del opcode del M1 que se descarta
// 2.- Si estaba en un HALT esperando una INT, lo saca de la espera
MemIoImpl.fetchOpcode(regPC);
MemIoImpl.interruptHandlingTime(1);
if (halted) {
halted = false;
regPC = (regPC + 1) & 0xffff;
}

regR++;
ffIFF1 = false;
push(regPC); // 3+3 t-estados + contended si procede
Expand Down Expand Up @@ -1793,41 +1862,43 @@ public final void execute(int statesLimit) {
opCode = NotifyImpl.breakpoint(regPC, opCode);
}

regPC = (regPC + 1) & 0xffff;
if (!halted) {
regPC = (regPC + 1) & 0xffff;

// El prefijo 0xCB no cuenta para esta guerra.
// En CBxx todas las xx producen un código válido
// de instrucción, incluyendo CBCB.
switch (prefixOpcode) {
case 0x00:
flagQ = pendingEI = false;
decodeOpcode(opCode);
break;
case 0xDD:
prefixOpcode = 0;
regIX = decodeDDFD(opCode, regIX);
break;
case 0xED:
prefixOpcode = 0;
decodeED(opCode);
break;
case 0xFD:
prefixOpcode = 0;
regIY = decodeDDFD(opCode, regIY);
break;
default:
System.out.println(String.format("ERROR!: prefixOpcode = %02x, opCode = %02x", prefixOpcode, opCode));
}
// El prefijo 0xCB no cuenta para esta guerra.
// En CBxx todas las xx producen un código válido
// de instrucción, incluyendo CBCB.
switch (prefixOpcode) {
case 0x00:
flagQ = pendingEI = false;
decodeOpcode(opCode);
break;
case 0xDD:
prefixOpcode = 0;
regIX = decodeDDFD(opCode, regIX);
break;
case 0xED:
prefixOpcode = 0;
decodeED(opCode);
break;
case 0xFD:
prefixOpcode = 0;
regIY = decodeDDFD(opCode, regIY);
break;
default:
log.error(String.format("ERROR!: prefixOpcode = %02x, opCode = %02x", prefixOpcode, opCode));
}

if (prefixOpcode != 0x00)
continue;
if (prefixOpcode != 0x00) {
continue;
}

lastFlagQ = flagQ;
lastFlagQ = flagQ;

if (execDone) {
NotifyImpl.execDone();
if (execDone) {
NotifyImpl.execDone();
}
}

// Primero se comprueba NMI
// Si se activa NMI no se comprueba INT porque la siguiente
// instrucción debe ser la de 0x0066.
Expand Down Expand Up @@ -2435,7 +2506,6 @@ private void decodeOpcode(int opCode) {
break;
}
case 0x76: { /* HALT */
regPC = (regPC - 1) & 0xffff;
halted = true;
break;
}
Expand Down Expand Up @@ -5591,6 +5661,10 @@ private void decodeED(int opCode) {
regPC = (regPC - 2) & 0xffff;
memptr = regPC + 1;
MemIoImpl.addressOnBus((getRegDE() - 1) & 0xffff, 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= ((regPC >>> 8) & FLAG_53_MASK);
}
}
break;
}
Expand All @@ -5601,6 +5675,10 @@ private void decodeED(int opCode) {
regPC = (regPC - 2) & 0xffff;
memptr = regPC + 1;
MemIoImpl.addressOnBus((getRegHL() - 1) & 0xffff, 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= ((regPC >>> 8) & FLAG_53_MASK);
}
}
break;
}
Expand All @@ -5609,6 +5687,17 @@ private void decodeED(int opCode) {
if (regB != 0) {
regPC = (regPC - 2) & 0xffff;
MemIoImpl.addressOnBus((getRegHL() - 1) & 0xffff, 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= (regPC >>> 8) & FLAG_53_MASK;
if (carryFlag) {
int cpyB = regB;
sz5h3pnFlags &= ~(HALFCARRY_MASK | PARITY_MASK);
cpyB += (sz5h3pnFlags & ADDSUB_MASK) != 0 ? 0 : 1;
sz5h3pnFlags |= ((cpyB ^ regB) & HALFCARRY_MASK);
sz5h3pnFlags |= (sz53pn_addTable[(cpyB & 0x07)] & PARITY_MASK);
}
}
}
break;
}
Expand All @@ -5617,6 +5706,17 @@ private void decodeED(int opCode) {
if (regB != 0) {
regPC = (regPC - 2) & 0xffff;
MemIoImpl.addressOnBus(getRegBC(), 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= (regPC >>> 8) & FLAG_53_MASK;
if (carryFlag) {
int cpyB = regB;
sz5h3pnFlags &= ~(HALFCARRY_MASK | PARITY_MASK);
cpyB += (sz5h3pnFlags & ADDSUB_MASK) != 0 ? -1 : 1;
sz5h3pnFlags |= ((cpyB ^ regB) & HALFCARRY_MASK);
sz5h3pnFlags |= (sz53pn_addTable[(cpyB & 0x07)] & PARITY_MASK);
}
}
}
break;
}
Expand All @@ -5626,6 +5726,10 @@ private void decodeED(int opCode) {
regPC = (regPC - 2) & 0xffff;
memptr = regPC + 1;
MemIoImpl.addressOnBus((getRegDE() + 1) & 0xffff, 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= ((regPC >>> 8) & FLAG_53_MASK);
}
}
break;
}
Expand All @@ -5636,6 +5740,10 @@ private void decodeED(int opCode) {
regPC = (regPC - 2) & 0xffff;
memptr = regPC + 1;
MemIoImpl.addressOnBus((getRegHL() + 1) & 0xffff, 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= ((regPC >>> 8) & FLAG_53_MASK);
}
}
break;
}
Expand All @@ -5644,6 +5752,17 @@ private void decodeED(int opCode) {
if (regB != 0) {
regPC = (regPC - 2) & 0xffff;
MemIoImpl.addressOnBus((getRegHL() + 1) & 0xffff, 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= (regPC >>> 8) & FLAG_53_MASK;
if (carryFlag) {
int cpyB = regB;
sz5h3pnFlags &= ~(HALFCARRY_MASK | PARITY_MASK);
cpyB += (sz5h3pnFlags & ADDSUB_MASK) != 0 ? -1 : 1;
sz5h3pnFlags |= ((cpyB ^ regB) & HALFCARRY_MASK);
sz5h3pnFlags |= (sz53pn_addTable[(cpyB & 0x07)] & PARITY_MASK);
}
}
}
break;
}
Expand All @@ -5652,6 +5771,17 @@ private void decodeED(int opCode) {
if (regB != 0) {
regPC = (regPC - 2) & 0xffff;
MemIoImpl.addressOnBus(getRegBC(), 5);
if (ffIFF1 && !pendingEI && MemIoImpl.isActiveINT()) {
sz5h3pnFlags &= ~FLAG_53_MASK;
sz5h3pnFlags |= (regPC >>> 8) & FLAG_53_MASK;
if (carryFlag) {
int cpyB = regB;
sz5h3pnFlags &= ~(HALFCARRY_MASK | PARITY_MASK);
cpyB += (sz5h3pnFlags & ADDSUB_MASK) != 0 ? -1 : 1;
sz5h3pnFlags |= ((cpyB ^ regB) & HALFCARRY_MASK);
sz5h3pnFlags |= (sz53pn_addTable[(cpyB & 0x07)] & PARITY_MASK);
}
}
}
break;
}
Expand Down

0 comments on commit 2a685f7

Please sign in to comment.