Add finer control of memory and IO events in the Z80 implementation. Allows small tidy of the halt condition.

Signed-off-by: Adrian Conlon <adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2020-03-23 21:56:23 +00:00
parent 99f0712801
commit dcb809d8f9
4 changed files with 56 additions and 46 deletions

View File

@ -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);
}

View File

@ -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;

View File

@ -51,6 +51,18 @@ namespace EightBit {
Signal<Z80> ExecutingInstruction;
Signal<Z80> ExecutedInstruction;
Signal<EventArgs> ReadingMemory;
Signal<EventArgs> ReadMemory;
Signal<EventArgs> WritingMemory;
Signal<EventArgs> WrittenMemory;
Signal<EventArgs> ReadingIO;
Signal<EventArgs> ReadIO;
Signal<EventArgs> WritingIO;
Signal<EventArgs> 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();

View File

@ -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());