From dcb809d8f90ff9af5b70b4ee08192225ad4b6126 Mon Sep 17 00:00:00 2001 From: Adrian Conlon Date: Mon, 23 Mar 2020 21:56:23 +0000 Subject: [PATCH] Add finer control of memory and IO events in the Z80 implementation. Allows small tidy of the halt condition. Signed-off-by: Adrian Conlon --- Z80/fusetest_Z80/FuseTestRunner.cpp | 30 +++++++++------------- Z80/fusetest_Z80/FuseTestRunner.h | 1 - Z80/inc/Z80.h | 40 +++++++++++++++++++++++------ Z80/src/Z80.cpp | 31 +++++++++------------- 4 files changed, 56 insertions(+), 46 deletions(-) diff --git a/Z80/fusetest_Z80/FuseTestRunner.cpp b/Z80/fusetest_Z80/FuseTestRunner.cpp index 04f83c2..10a11cf 100644 --- a/Z80/fusetest_Z80/FuseTestRunner.cpp +++ b/Z80/fusetest_Z80/FuseTestRunner.cpp @@ -21,28 +21,21 @@ Fuse::TestRunner::TestRunner(const Test& test, const ExpectedTestResult& result) std::cout << "**** Cycle count: " << cpu.cycles() << std::endl; }); - ReadByte.connect([this](EightBit::EventArgs&) { - addActualEvent(currentBusAccessType() + "R"); + m_cpu.ReadMemory.connect([this](EightBit::EventArgs&) { + addActualEvent("MR"); }); - WrittenByte.connect([this](EightBit::EventArgs&) { - addActualEvent(currentBusAccessType() + "W"); + m_cpu.WrittenMemory.connect([this](EightBit::EventArgs&) { + addActualEvent("MW"); }); -} -std::string Fuse::TestRunner::currentBusAccessType() { + m_cpu.ReadIO.connect([this](EightBit::EventArgs&) { + addActualEvent("PR"); + }); - const bool ioRequest = m_cpu.requestingIO(); - const bool memoryRequest = m_cpu.requestingMemory(); - if (ioRequest && memoryRequest) - throw std::logic_error("Invalid bus state (both IORQ and MREQ lowered"); - - if (ioRequest) - return "P"; - if (memoryRequest) - return "M"; - - throw std::logic_error("Invalid bus state (neither IORQ and MREQ lowered"); + m_cpu.WrittenIO.connect([this](EightBit::EventArgs&) { + addActualEvent("PW"); + }); } void Fuse::TestRunner::addActualEvent(const std::string& specifier) { @@ -50,7 +43,8 @@ void Fuse::TestRunner::addActualEvent(const std::string& specifier) { actual.address = ADDRESS().word; actual.cycles = m_totalCycles + m_cpu.cycles(); actual.specifier = specifier; - actual.value = DATA(); + if (!boost::algorithm::ends_with(specifier, "C")) + actual.value = DATA(); actual.valid = true; m_actualEvents.events.push_back(actual); } diff --git a/Z80/fusetest_Z80/FuseTestRunner.h b/Z80/fusetest_Z80/FuseTestRunner.h index 2dbdb53..e6697f6 100644 --- a/Z80/fusetest_Z80/FuseTestRunner.h +++ b/Z80/fusetest_Z80/FuseTestRunner.h @@ -40,7 +40,6 @@ namespace Fuse { const std::string& lowDescription, EightBit::register16_t actual, EightBit::register16_t expected) const; - std::string currentBusAccessType(); void addActualEvent(const std::string& specifier); void dumpExpectedEvents() const; void dumpActualEvents() const; diff --git a/Z80/inc/Z80.h b/Z80/inc/Z80.h index f14d505..63fc52b 100644 --- a/Z80/inc/Z80.h +++ b/Z80/inc/Z80.h @@ -51,6 +51,18 @@ namespace EightBit { Signal ExecutingInstruction; Signal ExecutedInstruction; + Signal ReadingMemory; + Signal ReadMemory; + + Signal WritingMemory; + Signal WrittenMemory; + + Signal ReadingIO; + Signal ReadIO; + + Signal WritingIO; + Signal WrittenIO; + int execute() final; int step() final; @@ -189,10 +201,28 @@ namespace EightBit { // 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() { + // + // 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 + uint8_t fetchInitialOpCode() { tick(); lowerM1(); - const auto returned = IntelProcessor::memoryRead(PC()); + auto returned = IntelProcessor::memoryRead(PC()); + if (UNLIKELY(lowered(HALT()))) + returned = 0; // NOP + else + PC()++; raiseM1(); BUS().ADDRESS() = { REFRESH(), IV() }; lowerRFSH(); @@ -202,12 +232,6 @@ namespace EightBit { return returned; } - uint8_t fetchInitialOpCode() { - const auto returned = readInitialOpCode(); - ++PC(); - return returned; - } - [[nodiscard]] auto& HL2() { if (LIKELY(!m_displaced)) return HL(); diff --git a/Z80/src/Z80.cpp b/Z80/src/Z80.cpp index 1f7cc68..6a85001 100644 --- a/Z80/src/Z80.cpp +++ b/Z80/src/Z80.cpp @@ -58,17 +58,21 @@ EightBit::register16_t& EightBit::Z80::HL() { } void EightBit::Z80::memoryWrite() { + WritingMemory.fire(EventArgs::empty()); tick(2); lowerMREQ(); IntelProcessor::memoryWrite(); raiseMREQ(); + WrittenMemory.fire(EventArgs::empty()); } uint8_t EightBit::Z80::memoryRead() { + ReadingMemory.fire(EventArgs::empty()); tick(2); lowerMREQ(); const auto returned = IntelProcessor::memoryRead(); raiseMREQ(); + ReadMemory.fire(EventArgs::empty()); return returned; } @@ -661,10 +665,15 @@ void EightBit::Z80::portWrite(const uint8_t port) { } void EightBit::Z80::portWrite() { + WritingIO.fire(EventArgs::empty()); lowerIORQ(); busWrite(); raiseIORQ(); - tick(3); + WrittenIO.fire(EventArgs::empty()); + tick(3); // I guess this means the extra three ticks on + // port writing are just a quirk of the + // individual instructions, rather than a + // fundamental aspect of port IO. Hmmm. } uint8_t EightBit::Z80::portRead(const uint8_t port) { @@ -674,9 +683,11 @@ uint8_t EightBit::Z80::portRead(const uint8_t port) { } uint8_t EightBit::Z80::portRead() { + ReadingIO.fire(EventArgs::empty()); lowerIORQ(); const auto returned = busRead(); raiseIORQ(); + ReadIO.fire(EventArgs::empty()); tick(3); return returned; } @@ -700,24 +711,6 @@ int EightBit::Z80::step() { 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(); - IntelProcessor::execute(0); // NOP - handled = true; } if (!handled) IntelProcessor::execute(fetchInitialOpCode());