mirror of
https://github.com/MoleskiCoder/EightBit.git
synced 2025-01-11 02:29:50 +00:00
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:
parent
99f0712801
commit
dcb809d8f9
@ -21,28 +21,21 @@ Fuse::TestRunner::TestRunner(const Test& test, const ExpectedTestResult& result)
|
|||||||
std::cout << "**** Cycle count: " << cpu.cycles() << std::endl;
|
std::cout << "**** Cycle count: " << cpu.cycles() << std::endl;
|
||||||
});
|
});
|
||||||
|
|
||||||
ReadByte.connect([this](EightBit::EventArgs&) {
|
m_cpu.ReadMemory.connect([this](EightBit::EventArgs&) {
|
||||||
addActualEvent(currentBusAccessType() + "R");
|
addActualEvent("MR");
|
||||||
});
|
});
|
||||||
|
|
||||||
WrittenByte.connect([this](EightBit::EventArgs&) {
|
m_cpu.WrittenMemory.connect([this](EightBit::EventArgs&) {
|
||||||
addActualEvent(currentBusAccessType() + "W");
|
addActualEvent("MW");
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
std::string Fuse::TestRunner::currentBusAccessType() {
|
m_cpu.ReadIO.connect([this](EightBit::EventArgs&) {
|
||||||
|
addActualEvent("PR");
|
||||||
|
});
|
||||||
|
|
||||||
const bool ioRequest = m_cpu.requestingIO();
|
m_cpu.WrittenIO.connect([this](EightBit::EventArgs&) {
|
||||||
const bool memoryRequest = m_cpu.requestingMemory();
|
addActualEvent("PW");
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fuse::TestRunner::addActualEvent(const std::string& specifier) {
|
void Fuse::TestRunner::addActualEvent(const std::string& specifier) {
|
||||||
@ -50,6 +43,7 @@ void Fuse::TestRunner::addActualEvent(const std::string& specifier) {
|
|||||||
actual.address = ADDRESS().word;
|
actual.address = ADDRESS().word;
|
||||||
actual.cycles = m_totalCycles + m_cpu.cycles();
|
actual.cycles = m_totalCycles + m_cpu.cycles();
|
||||||
actual.specifier = specifier;
|
actual.specifier = specifier;
|
||||||
|
if (!boost::algorithm::ends_with(specifier, "C"))
|
||||||
actual.value = DATA();
|
actual.value = DATA();
|
||||||
actual.valid = true;
|
actual.valid = true;
|
||||||
m_actualEvents.events.push_back(actual);
|
m_actualEvents.events.push_back(actual);
|
||||||
|
@ -40,7 +40,6 @@ namespace Fuse {
|
|||||||
const std::string& lowDescription,
|
const std::string& lowDescription,
|
||||||
EightBit::register16_t actual, EightBit::register16_t expected) const;
|
EightBit::register16_t actual, EightBit::register16_t expected) const;
|
||||||
|
|
||||||
std::string currentBusAccessType();
|
|
||||||
void addActualEvent(const std::string& specifier);
|
void addActualEvent(const std::string& specifier);
|
||||||
void dumpExpectedEvents() const;
|
void dumpExpectedEvents() const;
|
||||||
void dumpActualEvents() const;
|
void dumpActualEvents() const;
|
||||||
|
@ -51,6 +51,18 @@ namespace EightBit {
|
|||||||
Signal<Z80> ExecutingInstruction;
|
Signal<Z80> ExecutingInstruction;
|
||||||
Signal<Z80> ExecutedInstruction;
|
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 execute() final;
|
||||||
int step() 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
|
// 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
|
// refresh dynamic memories. The CPU uses this time to decode and execute the fetched
|
||||||
// instruction so that no other concurrent operation can be performed.
|
// 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();
|
tick();
|
||||||
lowerM1();
|
lowerM1();
|
||||||
const auto returned = IntelProcessor::memoryRead(PC());
|
auto returned = IntelProcessor::memoryRead(PC());
|
||||||
|
if (UNLIKELY(lowered(HALT())))
|
||||||
|
returned = 0; // NOP
|
||||||
|
else
|
||||||
|
PC()++;
|
||||||
raiseM1();
|
raiseM1();
|
||||||
BUS().ADDRESS() = { REFRESH(), IV() };
|
BUS().ADDRESS() = { REFRESH(), IV() };
|
||||||
lowerRFSH();
|
lowerRFSH();
|
||||||
@ -202,12 +232,6 @@ namespace EightBit {
|
|||||||
return returned;
|
return returned;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t fetchInitialOpCode() {
|
|
||||||
const auto returned = readInitialOpCode();
|
|
||||||
++PC();
|
|
||||||
return returned;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto& HL2() {
|
[[nodiscard]] auto& HL2() {
|
||||||
if (LIKELY(!m_displaced))
|
if (LIKELY(!m_displaced))
|
||||||
return HL();
|
return HL();
|
||||||
|
@ -58,17 +58,21 @@ EightBit::register16_t& EightBit::Z80::HL() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EightBit::Z80::memoryWrite() {
|
void EightBit::Z80::memoryWrite() {
|
||||||
|
WritingMemory.fire(EventArgs::empty());
|
||||||
tick(2);
|
tick(2);
|
||||||
lowerMREQ();
|
lowerMREQ();
|
||||||
IntelProcessor::memoryWrite();
|
IntelProcessor::memoryWrite();
|
||||||
raiseMREQ();
|
raiseMREQ();
|
||||||
|
WrittenMemory.fire(EventArgs::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t EightBit::Z80::memoryRead() {
|
uint8_t EightBit::Z80::memoryRead() {
|
||||||
|
ReadingMemory.fire(EventArgs::empty());
|
||||||
tick(2);
|
tick(2);
|
||||||
lowerMREQ();
|
lowerMREQ();
|
||||||
const auto returned = IntelProcessor::memoryRead();
|
const auto returned = IntelProcessor::memoryRead();
|
||||||
raiseMREQ();
|
raiseMREQ();
|
||||||
|
ReadMemory.fire(EventArgs::empty());
|
||||||
return returned;
|
return returned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,10 +665,15 @@ void EightBit::Z80::portWrite(const uint8_t port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EightBit::Z80::portWrite() {
|
void EightBit::Z80::portWrite() {
|
||||||
|
WritingIO.fire(EventArgs::empty());
|
||||||
lowerIORQ();
|
lowerIORQ();
|
||||||
busWrite();
|
busWrite();
|
||||||
raiseIORQ();
|
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) {
|
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() {
|
uint8_t EightBit::Z80::portRead() {
|
||||||
|
ReadingIO.fire(EventArgs::empty());
|
||||||
lowerIORQ();
|
lowerIORQ();
|
||||||
const auto returned = busRead();
|
const auto returned = busRead();
|
||||||
raiseIORQ();
|
raiseIORQ();
|
||||||
|
ReadIO.fire(EventArgs::empty());
|
||||||
tick(3);
|
tick(3);
|
||||||
return returned;
|
return returned;
|
||||||
}
|
}
|
||||||
@ -700,24 +711,6 @@ int EightBit::Z80::step() {
|
|||||||
handleINT();
|
handleINT();
|
||||||
handled = true;
|
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)
|
if (!handled)
|
||||||
IntelProcessor::execute(fetchInitialOpCode());
|
IntelProcessor::execute(fetchInitialOpCode());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user