mirror of
https://github.com/MoleskiCoder/EightBit.git
synced 2025-01-01 11:29:17 +00:00
Sync with .Net version
Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
parent
2d7f87018c
commit
acf6cf6c71
@ -69,7 +69,19 @@ namespace EightBit {
|
||||
[[nodiscard]] auto& IYH() { return IY().high; }
|
||||
[[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& IV() { return iv; }
|
||||
[[nodiscard]] auto& IM() { return m_interruptMode; }
|
||||
[[nodiscard]] auto& IFF1() { return m_iff1; }
|
||||
@ -85,6 +97,13 @@ namespace EightBit {
|
||||
|
||||
DECLARE_PIN_INPUT(NMI)
|
||||
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 system’s address bus can be used as a refresh address to the system’s
|
||||
// dynamic memories.
|
||||
DECLARE_PIN_OUTPUT(RFSH)
|
||||
|
||||
DECLARE_PIN_OUTPUT(MREQ)
|
||||
DECLARE_PIN_OUTPUT(IORQ)
|
||||
DECLARE_PIN_OUTPUT(RD)
|
||||
@ -137,10 +156,37 @@ namespace EightBit {
|
||||
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();
|
||||
const auto returned = fetchByte();
|
||||
const auto returned = IntelProcessor::busRead(PC());
|
||||
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;
|
||||
}
|
||||
|
||||
|
126
Z80/src/Z80.cpp
126
Z80/src/Z80.cpp
@ -28,13 +28,14 @@ EightBit::Z80::Z80(Bus& bus, InputOutput& ports)
|
||||
m_prefixCB = m_prefixDD = m_prefixED = m_prefixFD = false;
|
||||
});
|
||||
|
||||
LoweredM1.connect([this](EventArgs) {
|
||||
RaisedM1.connect([this](EventArgs) {
|
||||
++REFRESH();
|
||||
});
|
||||
}
|
||||
|
||||
DEFINE_PIN_LEVEL_CHANGERS(NMI, Z80);
|
||||
DEFINE_PIN_LEVEL_CHANGERS(M1, Z80);
|
||||
DEFINE_PIN_LEVEL_CHANGERS(RFSH, Z80);
|
||||
DEFINE_PIN_LEVEL_CHANGERS(MREQ, Z80);
|
||||
DEFINE_PIN_LEVEL_CHANGERS(IORQ, Z80);
|
||||
DEFINE_PIN_LEVEL_CHANGERS(RD, Z80);
|
||||
@ -725,12 +726,28 @@ int EightBit::Z80::step() {
|
||||
handleNMI();
|
||||
handled = true;
|
||||
} else if (lowered(INT())) {
|
||||
raiseINT();
|
||||
raiseHALT();
|
||||
if (IFF1()) {
|
||||
handleINT();
|
||||
handled = true;
|
||||
}
|
||||
} 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
|
||||
handled = true;
|
||||
}
|
||||
@ -800,10 +817,10 @@ void EightBit::Z80::executeCB(const int x, const int y, const int z) {
|
||||
UNREACHABLE;
|
||||
}
|
||||
F() = adjustSZP<Z80>(F(), operand);
|
||||
tick(8);
|
||||
tick(4);
|
||||
break;
|
||||
} case 1: // BIT y, r[z]
|
||||
tick(8);
|
||||
tick(4);
|
||||
bit(F(), y, operand);
|
||||
if (indirect) {
|
||||
F() = adjustXY<Z80>(F(), MEMPTR().high);
|
||||
@ -813,11 +830,11 @@ void EightBit::Z80::executeCB(const int x, const int y, const int z) {
|
||||
}
|
||||
break;
|
||||
case 2: // RES y, r[z]
|
||||
tick(8);
|
||||
tick(4);
|
||||
operand = res(y, operand);
|
||||
break;
|
||||
case 3: // SET y, r[z]
|
||||
tick(8);
|
||||
tick(4);
|
||||
operand = set(y, operand);
|
||||
break;
|
||||
default:
|
||||
@ -843,7 +860,6 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
|
||||
switch (x) {
|
||||
case 0:
|
||||
case 3: // Invalid instruction, equivalent to NONI followed by NOP
|
||||
tick(8);
|
||||
break;
|
||||
case 1:
|
||||
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());
|
||||
F() = adjustSZPXY<Z80>(F(), BUS().DATA());
|
||||
F() = clearBit(F(), NF | HC);
|
||||
tick(12);
|
||||
tick(4);
|
||||
break;
|
||||
case 1: // Output to port with 16-bit address
|
||||
(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]
|
||||
BUS().DATA() = R(y);
|
||||
writePort();
|
||||
tick(12);
|
||||
tick(4);
|
||||
break;
|
||||
case 2: // 16-bit add/subtract with carry
|
||||
switch (q) {
|
||||
@ -876,7 +892,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(15);
|
||||
tick(7);
|
||||
break;
|
||||
case 3: // Retrieve/store register pair from/to immediate address
|
||||
BUS().ADDRESS() = fetchWord();
|
||||
@ -890,11 +906,10 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(20);
|
||||
tick(12);
|
||||
break;
|
||||
case 4: // Negate accumulator
|
||||
A() = neg(F(), A());
|
||||
tick(8);
|
||||
break;
|
||||
case 5: // Return from interrupt
|
||||
switch (y) {
|
||||
@ -905,7 +920,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
|
||||
retn(); // RETN
|
||||
break;
|
||||
}
|
||||
tick(14);
|
||||
tick(6);
|
||||
break;
|
||||
case 6: // Set interrupt mode
|
||||
switch (y) {
|
||||
@ -926,41 +941,39 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(8);
|
||||
break;
|
||||
case 7: // Assorted ops
|
||||
switch (y) {
|
||||
case 0: // LD I,A
|
||||
IV() = A();
|
||||
tick(9);
|
||||
tick();
|
||||
break;
|
||||
case 1: // LD R,A
|
||||
REFRESH() = A();
|
||||
tick(9);
|
||||
tick();
|
||||
break;
|
||||
case 2: // LD A,I
|
||||
F() = adjustSZXY<Z80>(F(), A() = IV());
|
||||
F() = clearBit(F(), NF | HC);
|
||||
F() = setBit(F(), PF, IFF2());
|
||||
tick(9);
|
||||
tick();
|
||||
break;
|
||||
case 3: // LD A,R
|
||||
F() = adjustSZXY<Z80>(F(), A() = REFRESH());
|
||||
F() = clearBit(F(), NF | HC);
|
||||
F() = setBit(F(), PF, IFF2());
|
||||
tick(9);
|
||||
tick();
|
||||
break;
|
||||
case 4: // RRD
|
||||
rrd(F(), HL(), A());
|
||||
tick(18);
|
||||
tick(10);
|
||||
break;
|
||||
case 5: // RLD
|
||||
rld(F(), HL(), A());
|
||||
tick(18);
|
||||
tick(10);
|
||||
break;
|
||||
case 6: // NOP
|
||||
case 7: // NOP
|
||||
tick(4);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
@ -1067,7 +1080,7 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
|
||||
}
|
||||
break;
|
||||
}
|
||||
tick(16);
|
||||
tick(8);
|
||||
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
|
||||
switch (y) {
|
||||
case 0: // NOP
|
||||
if (m_prefixDD)
|
||||
tick(4);
|
||||
tick(4);
|
||||
break;
|
||||
case 1: // EX AF AF'
|
||||
exxAF();
|
||||
tick(4);
|
||||
break;
|
||||
case 2: // DJNZ d
|
||||
if (jrConditional(--B()))
|
||||
tick(5);
|
||||
tick(8);
|
||||
tick(4);
|
||||
break;
|
||||
case 3: // JR d
|
||||
jr(fetchByte());
|
||||
tick(12);
|
||||
tick(8);
|
||||
break;
|
||||
case 4: // JR cc,d
|
||||
case 5:
|
||||
@ -1104,7 +1113,7 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
case 7:
|
||||
if (jrConditionalFlag(F(), y - 4))
|
||||
tick(5);
|
||||
tick(5);
|
||||
tick(3);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
@ -1114,11 +1123,11 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
switch (q) {
|
||||
case 0: // LD rp,nn
|
||||
RP(p) = fetchWord();
|
||||
tick(10);
|
||||
tick(6);
|
||||
break;
|
||||
case 1: // ADD HL,rp
|
||||
HL2() = add(F(), HL2(), RP(p));
|
||||
tick(11);
|
||||
tick(7);
|
||||
break;
|
||||
default:
|
||||
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().high = BUS().DATA() = A();
|
||||
busWrite();
|
||||
tick(7);
|
||||
tick(3);
|
||||
break;
|
||||
case 1: // LD (DE),A
|
||||
(MEMPTR() = BUS().ADDRESS() = DE())++;
|
||||
MEMPTR().high = BUS().DATA() = A();
|
||||
busWrite();
|
||||
tick(7);
|
||||
tick(3);
|
||||
break;
|
||||
case 2: // LD (nn),HL
|
||||
BUS().ADDRESS() = fetchWord();
|
||||
setWord(HL2());
|
||||
tick(16);
|
||||
tick(12);
|
||||
break;
|
||||
case 3: // LD (nn),A
|
||||
(MEMPTR() = BUS().ADDRESS() = fetchWord())++;
|
||||
MEMPTR().high = BUS().DATA() = A();
|
||||
busWrite();
|
||||
tick(13);
|
||||
tick(9);
|
||||
break;
|
||||
default:
|
||||
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)
|
||||
(MEMPTR() = BUS().ADDRESS() = BC())++;
|
||||
A() = busRead();
|
||||
tick(7);
|
||||
tick(3);
|
||||
break;
|
||||
case 1: // LD A,(DE)
|
||||
(MEMPTR() = BUS().ADDRESS() = DE())++;
|
||||
A() = busRead();
|
||||
tick(7);
|
||||
tick(3);
|
||||
break;
|
||||
case 2: // LD HL,(nn)
|
||||
BUS().ADDRESS() = fetchWord();
|
||||
HL2() = getWord();
|
||||
tick(16);
|
||||
tick(12);
|
||||
break;
|
||||
case 3: // LD A,(nn)
|
||||
(MEMPTR() = BUS().ADDRESS() = fetchWord())++;
|
||||
A() = busRead();
|
||||
tick(13);
|
||||
tick(9);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
@ -1196,13 +1205,12 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(6);
|
||||
tick(2);
|
||||
break;
|
||||
case 4: // 8-bit INC
|
||||
if (m_displaced && memoryY)
|
||||
fetchDisplacement();
|
||||
R(y, increment(F(), R(y)));
|
||||
tick(4);
|
||||
break;
|
||||
case 5: // 8-bit DEC
|
||||
if (memoryY) {
|
||||
@ -1211,7 +1219,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
fetchDisplacement();
|
||||
}
|
||||
R(y, decrement(F(), R(y)));
|
||||
tick(4);
|
||||
break;
|
||||
case 6: // 8-bit load immediate
|
||||
if (memoryY) {
|
||||
@ -1220,7 +1227,7 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
fetchDisplacement();
|
||||
}
|
||||
R(y, fetchByte()); // LD r,n
|
||||
tick(7);
|
||||
tick(3);
|
||||
break;
|
||||
case 7: // Assorted operations on accumulator/flags
|
||||
switch (y) {
|
||||
@ -1251,7 +1258,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(4);
|
||||
break;
|
||||
default:
|
||||
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
|
||||
tick(3);
|
||||
}
|
||||
tick(4);
|
||||
break;
|
||||
case 2: { // Operate on accumulator and register/memory location
|
||||
if (memoryZ) {
|
||||
@ -1332,7 +1337,6 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(4);
|
||||
break;
|
||||
}
|
||||
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
|
||||
if (returnConditionalFlag(F(), y))
|
||||
tick(6);
|
||||
tick(5);
|
||||
tick(1);
|
||||
break;
|
||||
case 1: // POP & various ops
|
||||
switch (q) {
|
||||
case 0: // POP rp2[p]
|
||||
RP2(p) = popWord();
|
||||
tick(10);
|
||||
tick(6);
|
||||
break;
|
||||
case 1:
|
||||
switch (p) {
|
||||
case 0: // RET
|
||||
ret();
|
||||
tick(10);
|
||||
tick(6);
|
||||
break;
|
||||
case 1: // EXX
|
||||
exx();
|
||||
tick(4);
|
||||
break;
|
||||
case 2: // JP (HL)
|
||||
jump(HL2());
|
||||
tick(4);
|
||||
break;
|
||||
case 3: // LD SP,HL
|
||||
SP() = HL2();
|
||||
tick(4);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
@ -1376,13 +1377,13 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
break;
|
||||
case 2: // Conditional jump
|
||||
jumpConditionalFlag(F(), y);
|
||||
tick(10);
|
||||
tick(6);
|
||||
break;
|
||||
case 3: // Assorted operations
|
||||
switch (y) {
|
||||
case 0: // JP nn
|
||||
jump(MEMPTR() = fetchWord());
|
||||
tick(10);
|
||||
tick(6);
|
||||
break;
|
||||
case 1: // CB prefix
|
||||
m_prefixCB = true;
|
||||
@ -1395,27 +1396,24 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
|
||||
break;
|
||||
case 2: // OUT (n),A
|
||||
writePort(fetchByte());
|
||||
tick(11);
|
||||
tick(7);
|
||||
break;
|
||||
case 3: // IN A,(n)
|
||||
A() = readPort(fetchByte());
|
||||
tick(11);
|
||||
tick(7);
|
||||
break;
|
||||
case 4: // EX (SP),HL
|
||||
xhtl(HL2());
|
||||
tick(19);
|
||||
tick(15);
|
||||
break;
|
||||
case 5: // EX DE,HL
|
||||
std::swap(DE(), HL());
|
||||
tick(4);
|
||||
break;
|
||||
case 6: // DI
|
||||
di();
|
||||
tick(4);
|
||||
break;
|
||||
case 7: // EI
|
||||
ei();
|
||||
tick(4);
|
||||
break;
|
||||
default:
|
||||
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
|
||||
if (callConditionalFlag(F(), y))
|
||||
tick(7);
|
||||
tick(10);
|
||||
tick(6);
|
||||
break;
|
||||
case 5: // PUSH & various ops
|
||||
switch (q) {
|
||||
case 0: // PUSH rp2[p]
|
||||
pushWord(RP2(p));
|
||||
tick(11);
|
||||
tick(7);
|
||||
break;
|
||||
case 1:
|
||||
switch (p) {
|
||||
case 0: // CALL nn
|
||||
call(MEMPTR() = fetchWord());
|
||||
tick(17);
|
||||
tick(13);
|
||||
break;
|
||||
case 1: // DD prefix
|
||||
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:
|
||||
UNREACHABLE;
|
||||
}
|
||||
tick(7);
|
||||
tick(3);
|
||||
break;
|
||||
}
|
||||
case 7: // Restart: RST y * 8
|
||||
restart(y << 3);
|
||||
tick(11);
|
||||
tick(7);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
|
Loading…
Reference in New Issue
Block a user