Sync with .Net version

Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2019-11-16 23:37:57 +00:00
parent 2d7f87018c
commit acf6cf6c71
2 changed files with 110 additions and 66 deletions

View File

@ -69,7 +69,19 @@ namespace EightBit {
[[nodiscard]] auto& IYH() { return IY().high; } [[nodiscard]] auto& IYH() { return IY().high; }
[[nodiscard]] auto& IYL() { return IY().low; } [[nodiscard]] auto& IYL() { return IY().low; }
// ** From the Z80 CPU User Manual
// Memory Refresh(R) Register.The Z80 CPU contains a memory refresh counter,
// enabling dynamic memories to be used with the same ease as static memories.Seven bits
// of this 8-bit register are automatically incremented after each instruction fetch.The eighth
// bit remains as programmed, resulting from an LD R, A instruction. The data in the refresh
// counter is sent out on the lower portion of the address bus along with a refresh control
// signal while the CPU is decoding and executing the fetched instruction. This mode of refresh
// is transparent to the programmer and does not slow the CPU operation.The programmer
// can load the R register for testing purposes, but this register is normally not used by the
// programmer. During refresh, the contents of the I Register are placed on the upper eight
// bits of the address bus.
[[nodiscard]] auto& REFRESH() { return m_refresh; } [[nodiscard]] auto& REFRESH() { return m_refresh; }
[[nodiscard]] auto& IV() { return iv; } [[nodiscard]] auto& IV() { return iv; }
[[nodiscard]] auto& IM() { return m_interruptMode; } [[nodiscard]] auto& IM() { return m_interruptMode; }
[[nodiscard]] auto& IFF1() { return m_iff1; } [[nodiscard]] auto& IFF1() { return m_iff1; }
@ -85,6 +97,13 @@ namespace EightBit {
DECLARE_PIN_INPUT(NMI) DECLARE_PIN_INPUT(NMI)
DECLARE_PIN_OUTPUT(M1) DECLARE_PIN_OUTPUT(M1)
// ** From the Z80 CPU User Manual
// RFSH.Refresh(output, active Low). RFSH, together with MREQ, indicates that the lower
// seven bits of the systems address bus can be used as a refresh address to the systems
// dynamic memories.
DECLARE_PIN_OUTPUT(RFSH)
DECLARE_PIN_OUTPUT(MREQ) DECLARE_PIN_OUTPUT(MREQ)
DECLARE_PIN_OUTPUT(IORQ) DECLARE_PIN_OUTPUT(IORQ)
DECLARE_PIN_OUTPUT(RD) DECLARE_PIN_OUTPUT(RD)
@ -137,10 +156,37 @@ namespace EightBit {
m_displacement = fetchByte(); m_displacement = fetchByte();
} }
uint8_t fetchInitialOpCode() { // ** From the Z80 CPU User Manual
// Figure 5 depicts the timing during an M1 (op code fetch) cycle. The Program Counter is
// placed on the address bus at the beginning of the M1 cycle. One half clock cycle later, the
// MREQ signal goes active. At this time, the address to memory has had time to stabilize so
// that the falling edge of MREQ can be used directly as a chip enable clock to dynamic
// memories. The RD line also goes active to indicate that the memory read data should be
// enabled onto the CPU data bus. The CPU samples the data from the memory space on the
// data bus with the rising edge of the clock of state T3, and this same edge is used by the
// CPU to turn off the RD and MREQ signals. As a result, the data is sampled by the CPU
// before the RD signal becomes inactive. Clock states T3 and T4 of a fetch cycle are used to
// refresh dynamic memories. The CPU uses this time to decode and execute the fetched
// instruction so that no other concurrent operation can be performed.
uint8_t readInitialOpCode() {
lowerM1(); lowerM1();
const auto returned = fetchByte(); const auto returned = IntelProcessor::busRead(PC());
raiseM1(); raiseM1();
tick(2);
BUS().ADDRESS().low = REFRESH();
BUS().ADDRESS().high = IV();
lowerRFSH();
lowerMREQ();
tick();
raiseMREQ();
raiseRFSH();
tick();
return returned;
}
uint8_t fetchInitialOpCode() {
const auto returned = readInitialOpCode();
++PC();
return returned; return returned;
} }

View File

@ -28,13 +28,14 @@ EightBit::Z80::Z80(Bus& bus, InputOutput& ports)
m_prefixCB = m_prefixDD = m_prefixED = m_prefixFD = false; m_prefixCB = m_prefixDD = m_prefixED = m_prefixFD = false;
}); });
LoweredM1.connect([this](EventArgs) { RaisedM1.connect([this](EventArgs) {
++REFRESH(); ++REFRESH();
}); });
} }
DEFINE_PIN_LEVEL_CHANGERS(NMI, Z80); DEFINE_PIN_LEVEL_CHANGERS(NMI, Z80);
DEFINE_PIN_LEVEL_CHANGERS(M1, Z80); DEFINE_PIN_LEVEL_CHANGERS(M1, Z80);
DEFINE_PIN_LEVEL_CHANGERS(RFSH, Z80);
DEFINE_PIN_LEVEL_CHANGERS(MREQ, Z80); DEFINE_PIN_LEVEL_CHANGERS(MREQ, Z80);
DEFINE_PIN_LEVEL_CHANGERS(IORQ, Z80); DEFINE_PIN_LEVEL_CHANGERS(IORQ, Z80);
DEFINE_PIN_LEVEL_CHANGERS(RD, Z80); DEFINE_PIN_LEVEL_CHANGERS(RD, Z80);
@ -725,12 +726,28 @@ int EightBit::Z80::step() {
handleNMI(); handleNMI();
handled = true; handled = true;
} else if (lowered(INT())) { } else if (lowered(INT())) {
raiseINT();
raiseHALT(); raiseHALT();
if (IFF1()) { if (IFF1()) {
handleINT(); handleINT();
handled = true; handled = true;
} }
} else if (lowered(HALT())) { } else if (lowered(HALT())) {
// ** From the Z80 CPU User Manual
// When a software HALT instruction is executed, the CPU executes NOPs until an interrupt
// is received(either a nonmaskable or a maskable interrupt while the interrupt flip-flop is
// enabled). The two interrupt lines are sampled with the rising clock edge during each T4
// state as depicted in Figure 11.If a nonmaskable interrupt is received or a maskable interrupt
// is received and the interrupt enable flip-flop is set, then the HALT state is exited on
// the next rising clock edge.The following cycle is an interrupt acknowledge cycle corresponding
// to the type of interrupt that was received.If both are received at this time, then
// the nonmaskable interrupt is acknowledged because it is the highest priority.The purpose
// of executing NOP instructions while in the HALT state is to keep the memory refresh signals
// active.Each cycle in the HALT state is a normal M1(fetch) cycle except that the data
// received from the memory is ignored and an NOP instruction is forced internally to the
// CPU.The HALT acknowledge signal is active during this time indicating that the processor
// is in the HALT state.
const auto discarded = readInitialOpCode();
execute(0); // NOP execute(0); // NOP
handled = true; handled = true;
} }
@ -800,10 +817,10 @@ void EightBit::Z80::executeCB(const int x, const int y, const int z) {
UNREACHABLE; UNREACHABLE;
} }
F() = adjustSZP<Z80>(F(), operand); F() = adjustSZP<Z80>(F(), operand);
tick(8); tick(4);
break; break;
} case 1: // BIT y, r[z] } case 1: // BIT y, r[z]
tick(8); tick(4);
bit(F(), y, operand); bit(F(), y, operand);
if (indirect) { if (indirect) {
F() = adjustXY<Z80>(F(), MEMPTR().high); F() = adjustXY<Z80>(F(), MEMPTR().high);
@ -813,11 +830,11 @@ void EightBit::Z80::executeCB(const int x, const int y, const int z) {
} }
break; break;
case 2: // RES y, r[z] case 2: // RES y, r[z]
tick(8); tick(4);
operand = res(y, operand); operand = res(y, operand);
break; break;
case 3: // SET y, r[z] case 3: // SET y, r[z]
tick(8); tick(4);
operand = set(y, operand); operand = set(y, operand);
break; break;
default: default:
@ -843,7 +860,6 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
switch (x) { switch (x) {
case 0: case 0:
case 3: // Invalid instruction, equivalent to NONI followed by NOP case 3: // Invalid instruction, equivalent to NONI followed by NOP
tick(8);
break; break;
case 1: case 1:
switch (z) { switch (z) {
@ -854,7 +870,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
R(y, BUS().DATA()); R(y, BUS().DATA());
F() = adjustSZPXY<Z80>(F(), BUS().DATA()); F() = adjustSZPXY<Z80>(F(), BUS().DATA());
F() = clearBit(F(), NF | HC); F() = clearBit(F(), NF | HC);
tick(12); tick(4);
break; break;
case 1: // Output to port with 16-bit address case 1: // Output to port with 16-bit address
(MEMPTR() = BUS().ADDRESS() = BC())++; (MEMPTR() = BUS().ADDRESS() = BC())++;
@ -863,7 +879,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
else // OUT (C),r[y] else // OUT (C),r[y]
BUS().DATA() = R(y); BUS().DATA() = R(y);
writePort(); writePort();
tick(12); tick(4);
break; break;
case 2: // 16-bit add/subtract with carry case 2: // 16-bit add/subtract with carry
switch (q) { switch (q) {
@ -876,7 +892,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(15); tick(7);
break; break;
case 3: // Retrieve/store register pair from/to immediate address case 3: // Retrieve/store register pair from/to immediate address
BUS().ADDRESS() = fetchWord(); BUS().ADDRESS() = fetchWord();
@ -890,11 +906,10 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(20); tick(12);
break; break;
case 4: // Negate accumulator case 4: // Negate accumulator
A() = neg(F(), A()); A() = neg(F(), A());
tick(8);
break; break;
case 5: // Return from interrupt case 5: // Return from interrupt
switch (y) { switch (y) {
@ -905,7 +920,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
retn(); // RETN retn(); // RETN
break; break;
} }
tick(14); tick(6);
break; break;
case 6: // Set interrupt mode case 6: // Set interrupt mode
switch (y) { switch (y) {
@ -926,41 +941,39 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(8);
break; break;
case 7: // Assorted ops case 7: // Assorted ops
switch (y) { switch (y) {
case 0: // LD I,A case 0: // LD I,A
IV() = A(); IV() = A();
tick(9); tick();
break; break;
case 1: // LD R,A case 1: // LD R,A
REFRESH() = A(); REFRESH() = A();
tick(9); tick();
break; break;
case 2: // LD A,I case 2: // LD A,I
F() = adjustSZXY<Z80>(F(), A() = IV()); F() = adjustSZXY<Z80>(F(), A() = IV());
F() = clearBit(F(), NF | HC); F() = clearBit(F(), NF | HC);
F() = setBit(F(), PF, IFF2()); F() = setBit(F(), PF, IFF2());
tick(9); tick();
break; break;
case 3: // LD A,R case 3: // LD A,R
F() = adjustSZXY<Z80>(F(), A() = REFRESH()); F() = adjustSZXY<Z80>(F(), A() = REFRESH());
F() = clearBit(F(), NF | HC); F() = clearBit(F(), NF | HC);
F() = setBit(F(), PF, IFF2()); F() = setBit(F(), PF, IFF2());
tick(9); tick();
break; break;
case 4: // RRD case 4: // RRD
rrd(F(), HL(), A()); rrd(F(), HL(), A());
tick(18); tick(10);
break; break;
case 5: // RLD case 5: // RLD
rld(F(), HL(), A()); rld(F(), HL(), A());
tick(18); tick(10);
break; break;
case 6: // NOP case 6: // NOP
case 7: // NOP case 7: // NOP
tick(4);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1067,7 +1080,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
} }
break; break;
} }
tick(16); tick(8);
break; break;
} }
} }
@ -1081,22 +1094,18 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
case 0: // Relative jumps and assorted ops case 0: // Relative jumps and assorted ops
switch (y) { switch (y) {
case 0: // NOP case 0: // NOP
if (m_prefixDD)
tick(4);
tick(4);
break; break;
case 1: // EX AF AF' case 1: // EX AF AF'
exxAF(); exxAF();
tick(4);
break; break;
case 2: // DJNZ d case 2: // DJNZ d
if (jrConditional(--B())) if (jrConditional(--B()))
tick(5); tick(5);
tick(8); tick(4);
break; break;
case 3: // JR d case 3: // JR d
jr(fetchByte()); jr(fetchByte());
tick(12); tick(8);
break; break;
case 4: // JR cc,d case 4: // JR cc,d
case 5: case 5:
@ -1104,7 +1113,7 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
case 7: case 7:
if (jrConditionalFlag(F(), y - 4)) if (jrConditionalFlag(F(), y - 4))
tick(5); tick(5);
tick(5); tick(3);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1114,11 +1123,11 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
switch (q) { switch (q) {
case 0: // LD rp,nn case 0: // LD rp,nn
RP(p) = fetchWord(); RP(p) = fetchWord();
tick(10); tick(6);
break; break;
case 1: // ADD HL,rp case 1: // ADD HL,rp
HL2() = add(F(), HL2(), RP(p)); HL2() = add(F(), HL2(), RP(p));
tick(11); tick(7);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1132,24 +1141,24 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
(MEMPTR() = BUS().ADDRESS() = BC())++; (MEMPTR() = BUS().ADDRESS() = BC())++;
MEMPTR().high = BUS().DATA() = A(); MEMPTR().high = BUS().DATA() = A();
busWrite(); busWrite();
tick(7); tick(3);
break; break;
case 1: // LD (DE),A case 1: // LD (DE),A
(MEMPTR() = BUS().ADDRESS() = DE())++; (MEMPTR() = BUS().ADDRESS() = DE())++;
MEMPTR().high = BUS().DATA() = A(); MEMPTR().high = BUS().DATA() = A();
busWrite(); busWrite();
tick(7); tick(3);
break; break;
case 2: // LD (nn),HL case 2: // LD (nn),HL
BUS().ADDRESS() = fetchWord(); BUS().ADDRESS() = fetchWord();
setWord(HL2()); setWord(HL2());
tick(16); tick(12);
break; break;
case 3: // LD (nn),A case 3: // LD (nn),A
(MEMPTR() = BUS().ADDRESS() = fetchWord())++; (MEMPTR() = BUS().ADDRESS() = fetchWord())++;
MEMPTR().high = BUS().DATA() = A(); MEMPTR().high = BUS().DATA() = A();
busWrite(); busWrite();
tick(13); tick(9);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1160,22 +1169,22 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
case 0: // LD A,(BC) case 0: // LD A,(BC)
(MEMPTR() = BUS().ADDRESS() = BC())++; (MEMPTR() = BUS().ADDRESS() = BC())++;
A() = busRead(); A() = busRead();
tick(7); tick(3);
break; break;
case 1: // LD A,(DE) case 1: // LD A,(DE)
(MEMPTR() = BUS().ADDRESS() = DE())++; (MEMPTR() = BUS().ADDRESS() = DE())++;
A() = busRead(); A() = busRead();
tick(7); tick(3);
break; break;
case 2: // LD HL,(nn) case 2: // LD HL,(nn)
BUS().ADDRESS() = fetchWord(); BUS().ADDRESS() = fetchWord();
HL2() = getWord(); HL2() = getWord();
tick(16); tick(12);
break; break;
case 3: // LD A,(nn) case 3: // LD A,(nn)
(MEMPTR() = BUS().ADDRESS() = fetchWord())++; (MEMPTR() = BUS().ADDRESS() = fetchWord())++;
A() = busRead(); A() = busRead();
tick(13); tick(9);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1196,13 +1205,12 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(6); tick(2);
break; break;
case 4: // 8-bit INC case 4: // 8-bit INC
if (m_displaced && memoryY) if (m_displaced && memoryY)
fetchDisplacement(); fetchDisplacement();
R(y, increment(F(), R(y))); R(y, increment(F(), R(y)));
tick(4);
break; break;
case 5: // 8-bit DEC case 5: // 8-bit DEC
if (memoryY) { if (memoryY) {
@ -1211,7 +1219,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
fetchDisplacement(); fetchDisplacement();
} }
R(y, decrement(F(), R(y))); R(y, decrement(F(), R(y)));
tick(4);
break; break;
case 6: // 8-bit load immediate case 6: // 8-bit load immediate
if (memoryY) { if (memoryY) {
@ -1220,7 +1227,7 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
fetchDisplacement(); fetchDisplacement();
} }
R(y, fetchByte()); // LD r,n R(y, fetchByte()); // LD r,n
tick(7); tick(3);
break; break;
case 7: // Assorted operations on accumulator/flags case 7: // Assorted operations on accumulator/flags
switch (y) { switch (y) {
@ -1251,7 +1258,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(4);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1295,7 +1301,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
if (memoryY || memoryZ) // M operations if (memoryY || memoryZ) // M operations
tick(3); tick(3);
} }
tick(4);
break; break;
case 2: { // Operate on accumulator and register/memory location case 2: { // Operate on accumulator and register/memory location
if (memoryZ) { if (memoryZ) {
@ -1332,7 +1337,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(4);
break; break;
} }
case 3: case 3:
@ -1340,31 +1344,28 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
case 0: // Conditional return case 0: // Conditional return
if (returnConditionalFlag(F(), y)) if (returnConditionalFlag(F(), y))
tick(6); tick(6);
tick(5); tick(1);
break; break;
case 1: // POP & various ops case 1: // POP & various ops
switch (q) { switch (q) {
case 0: // POP rp2[p] case 0: // POP rp2[p]
RP2(p) = popWord(); RP2(p) = popWord();
tick(10); tick(6);
break; break;
case 1: case 1:
switch (p) { switch (p) {
case 0: // RET case 0: // RET
ret(); ret();
tick(10); tick(6);
break; break;
case 1: // EXX case 1: // EXX
exx(); exx();
tick(4);
break; break;
case 2: // JP (HL) case 2: // JP (HL)
jump(HL2()); jump(HL2());
tick(4);
break; break;
case 3: // LD SP,HL case 3: // LD SP,HL
SP() = HL2(); SP() = HL2();
tick(4);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1376,13 +1377,13 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
break; break;
case 2: // Conditional jump case 2: // Conditional jump
jumpConditionalFlag(F(), y); jumpConditionalFlag(F(), y);
tick(10); tick(6);
break; break;
case 3: // Assorted operations case 3: // Assorted operations
switch (y) { switch (y) {
case 0: // JP nn case 0: // JP nn
jump(MEMPTR() = fetchWord()); jump(MEMPTR() = fetchWord());
tick(10); tick(6);
break; break;
case 1: // CB prefix case 1: // CB prefix
m_prefixCB = true; m_prefixCB = true;
@ -1395,27 +1396,24 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
break; break;
case 2: // OUT (n),A case 2: // OUT (n),A
writePort(fetchByte()); writePort(fetchByte());
tick(11); tick(7);
break; break;
case 3: // IN A,(n) case 3: // IN A,(n)
A() = readPort(fetchByte()); A() = readPort(fetchByte());
tick(11); tick(7);
break; break;
case 4: // EX (SP),HL case 4: // EX (SP),HL
xhtl(HL2()); xhtl(HL2());
tick(19); tick(15);
break; break;
case 5: // EX DE,HL case 5: // EX DE,HL
std::swap(DE(), HL()); std::swap(DE(), HL());
tick(4);
break; break;
case 6: // DI case 6: // DI
di(); di();
tick(4);
break; break;
case 7: // EI case 7: // EI
ei(); ei();
tick(4);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;
@ -1424,19 +1422,19 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
case 4: // Conditional call: CALL cc[y], nn case 4: // Conditional call: CALL cc[y], nn
if (callConditionalFlag(F(), y)) if (callConditionalFlag(F(), y))
tick(7); tick(7);
tick(10); tick(6);
break; break;
case 5: // PUSH & various ops case 5: // PUSH & various ops
switch (q) { switch (q) {
case 0: // PUSH rp2[p] case 0: // PUSH rp2[p]
pushWord(RP2(p)); pushWord(RP2(p));
tick(11); tick(7);
break; break;
case 1: case 1:
switch (p) { switch (p) {
case 0: // CALL nn case 0: // CALL nn
call(MEMPTR() = fetchWord()); call(MEMPTR() = fetchWord());
tick(17); tick(13);
break; break;
case 1: // DD prefix case 1: // DD prefix
m_displaced = m_prefixDD = true; m_displaced = m_prefixDD = true;
@ -1488,12 +1486,12 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
default: default:
UNREACHABLE; UNREACHABLE;
} }
tick(7); tick(3);
break; break;
} }
case 7: // Restart: RST y * 8 case 7: // Restart: RST y * 8
restart(y << 3); restart(y << 3);
tick(11); tick(7);
break; break;
default: default:
UNREACHABLE; UNREACHABLE;