Synchronise C++ code with C# code. Now Z80 passes HarteTests

This commit is contained in:
Adrian Conlon committed 2026-05-25 22:22:22 +01:00
1 parent 46f03aa759
commit e1ed8ea122
7 files changed
+110 -192

No files matched your search

+25 -22
View File
@@ -7,7 +7,7 @@ void checker_t::addActualCycle(const uint16_t address, const uint8_t value, cons
}
void checker_t::addActualCycle(const EightBit::register16_t address, const uint8_t value, const std::string& action) {
addActualCycle(address.word, value, action);
addActualCycle(address.joined, value, action);
}
void checker_t::dumpCycles(const std::string which, const actual_cycles_t& events) {
@@ -226,8 +226,8 @@ bool checker_t::checkState(test_t test) {
const auto final = test.final();
const auto pc_good = check("PC", final.pc(), cpu.PC().word);
const auto sp_good = check("SP", final.sp(), cpu.SP().word);
const auto pc_good = check("PC", final.pc(), cpu.PC().joined);
const auto sp_good = check("SP", final.sp(), cpu.SP().joined);
const auto a_good = check("A", final.a(), cpu.A());
const auto f_good = check("F", final.f(), cpu.F());
@@ -248,26 +248,29 @@ bool checker_t::checkState(test_t test) {
cpu.exxAF();
cpu.exx();
const auto af_alt_good = check("AF'", final.af_(), cpu.AF().word);
const auto bc_alt_good = check("BC'", final.bc_(), cpu.BC().word);
const auto de_alt_good = check("DE'", final.de_(), cpu.DE().word);
const auto hl_alt_good = check("DE'", final.de_(), cpu.DE().word);
const auto af_alt_good = check("AF'", final.af_(), cpu.AF().joined);
const auto bc_alt_good = check("BC'", final.bc_(), cpu.BC().joined);
const auto de_alt_good = check("DE'", final.de_(), cpu.DE().joined);
const auto hl_alt_good = check("HL'", final.hl_(), cpu.HL().joined);
cpu.exx();
cpu.exxAF();
const auto i_good = check("I", final.i(), cpu.IV());
const auto r_good = check("R", final.r(), (uint8_t)cpu.REFRESH());
const auto im_good = check("IM", final.im(), (uint8_t)cpu.IM());
const auto q_good = check("Q", final.im(), (uint8_t)cpu.IM());
const auto q_good = check("Q", final.q(), (uint8_t)cpu.Q());
const auto iff1_good = check("IFF1", final.iff1(), (uint8_t)cpu.IFF1());
const auto iff2_good = check("IFF2", final.iff2(), (uint8_t)cpu.IFF2());
const auto iff_good = iff1_good && iff2_good;
const auto memptr_good = check("MEMPTR", final.wz(), cpu.MEMPTR().word);
const auto memptr_good = check("MEMPTR", final.wz(), cpu.MEMPTR().joined);
const auto ix_good = check("IX", final.ix(), cpu.IX().word);
const auto iy_good = check("IY", final.iy(), cpu.IY().word);
const auto ix_good = check("IX", final.ix(), cpu.IX().joined);
const auto iy_good = check("IY", final.iy(), cpu.IY().joined);
bool ram_problem = false;
for (const auto entry : final.ram()) {
@@ -319,7 +322,7 @@ void checker_t::check(test_t test) {
runner().raisePOWER();
initialiseState(test);
const auto pc = cpu.PC().word;
const auto pc = cpu.PC().joined;
m_cycles = cpu.step();
runner().lowerPOWER();
@@ -336,11 +339,11 @@ void checker_t::check(test_t test) {
add_disassembly(pc);
const auto final = test.final();
raise("PC", final.pc(), cpu.PC().word);
raise("SP", final.sp(), cpu.SP().word);
raise("PC", final.pc(), cpu.PC().joined);
raise("SP", final.sp(), cpu.SP().joined);
raise("A", final.a(), cpu.A());
raiseFlags("F", final.a(), cpu.A());
raiseFlags("F", final.f(), cpu.F());
raise("B", final.b(), cpu.B());
raise("C", final.c(), cpu.C());
raise("D", final.d(), cpu.D());
@@ -351,10 +354,10 @@ void checker_t::check(test_t test) {
cpu.exx();
cpu.exxAF();
raise("'AF", final.af_(), cpu.AF().word);
raise("'BC", final.bc_(), cpu.BC().word);
raise("'DE", final.de_(), cpu.DE().word);
raise("'HL", final.hl_(), cpu.HL().word);
raise("'AF", final.af_(), cpu.AF().joined);
raise("'BC", final.bc_(), cpu.BC().joined);
raise("'DE", final.de_(), cpu.DE().joined);
raise("'HL", final.hl_(), cpu.HL().joined);
raise("I", final.i(), cpu.IV());
raise("R", final.r(), cpu.REFRESH());
@@ -366,10 +369,10 @@ void checker_t::check(test_t test) {
raise("IFF1", final.iff1(), (uint8_t)cpu.IFF1());
raise("IFF2", final.iff2(), (uint8_t)cpu.IFF2());
raise("WZ", final.wz(), cpu.MEMPTR().word);
raise("WZ", final.wz(), cpu.MEMPTR().joined);
raise("IX", final.ix(), cpu.IX().word);
raise("IY", final.iy(), cpu.IY().word);
raise("IX", final.ix(), cpu.IX().joined);
raise("IY", final.iy(), cpu.IY().joined);
os()
<< std::dec << std::setfill(' ')
+8 -5
View File
@@ -18,7 +18,7 @@ namespace EightBit {
public:
// ** 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
// 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)
@@ -208,8 +208,7 @@ namespace EightBit {
[[nodiscard]] constexpr auto subtracting() noexcept { return subtractingTest(F()); }
void adjustStatusFlags(uint8_t value) noexcept {
m_modifiedF = F();
F() = value;
F() = m_modifiedF = value;
}
void setBit(StatusBits flag) noexcept { return adjustStatusFlags(setBit(F(), flag)); }
@@ -386,6 +385,7 @@ namespace EightBit {
void lddr() noexcept;
void repeatBlockInstruction() noexcept;
void loadRepeat() noexcept;
void adjustBlockRepeatFlagsIO() noexcept;
void adjustBlockInputOutputFlags(int basis) noexcept;
void adjustBlockInFlagsIncrement() noexcept;
@@ -410,8 +410,8 @@ namespace EightBit {
void neg() noexcept;
void rrd(register16_t address, uint8_t& update) noexcept;
void rld(register16_t address, uint8_t& update) noexcept;
void rrd() noexcept;
void rld() noexcept;
void writePort(register16_t port) noexcept;
void writePort(uint8_t port) noexcept;
@@ -426,5 +426,8 @@ namespace EightBit {
void pushRegisterPair(int p);
void prefixCB();
void prefixDD();
void prefixED();
void prefixFD();
};
}
+73 -62
View File
@@ -21,15 +21,12 @@ EightBit::Z80::Z80(Bus& bus, InputOutput& ports) noexcept
REFRESH() = 0;
IV() = Mask8;
exxAF();
exx();
IX() = IY() = Mask16;
resetRegisterSet();
});
RaisedM1.connect([this](EventArgs) {
++REFRESH();
exx();
exxAF();
resetRegisterSet();
});
LoweredRESET.connect([this](EventArgs) {
@@ -114,7 +111,15 @@ bool EightBit::Z80::operator==(const EightBit::Z80& rhs) const noexcept {
DEFINE_PIN_LEVEL_CHANGERS(NMI, Z80);
DEFINE_PIN_LEVEL_CHANGERS(M1, Z80);
DEFINE_PIN_LEVEL_CHANGERS(RFSH, Z80);
void EightBit::Z80::raiseRFSH() noexcept {
if (lowered(RFSH())) {
RaisingRFSH.fire();
raise(RFSH());
++REFRESH();
RaisedRFSH.fire();
}
}
DEFINE_PIN_LEVEL_LOWER(RFSH, Z80);
DEFINE_PIN_LEVEL_CHANGERS(MREQ, Z80);
DEFINE_PIN_LEVEL_CHANGERS(IORQ, Z80);
DEFINE_PIN_LEVEL_CHANGERS(RD, Z80);
@@ -195,23 +200,23 @@ void EightBit::Z80::handleNMI() noexcept {
IFF2() = IFF1();
IFF1() = false;
lowerM1();
auto _ = BUS().DATA();
raiseM1();
restart(0x66);
}
uint8_t EightBit::Z80::readDataUnderInterrupt() noexcept {
lowerM1();
tick(2);
tick(3);
lowerIORQ();
tick();
const auto data = BUS().DATA();
auto _ = BUS().DATA();
raiseIORQ();
assert(cycles() == 4);
refreshMemory();
assert(cycles() == 5);
raiseM1();
tick();
return data;
return BUS().DATA();
}
void EightBit::Z80::handleINT() noexcept {
@@ -272,10 +277,8 @@ bool EightBit::Z80::convertCondition(int flag) noexcept {
}
void EightBit::Z80::returnConditionalFlag(const int flag) noexcept {
const auto condition = convertCondition(flag);
tick();
if (condition)
ret();
IntelProcessor::returnConditionalFlag(flag);
}
void EightBit::Z80::retn() noexcept {
@@ -473,19 +476,19 @@ uint8_t EightBit::Z80::rrc(uint8_t operand) noexcept {
}
uint8_t EightBit::Z80::rl(uint8_t operand) noexcept {
clearBit(NF | HC);
const auto carrying = carry();
setBit(CF, operand & Bit7);
const uint8_t result = (operand << 1) | carrying;
clearBit(NF | HC);
adjustXY(result);
return result;
}
uint8_t EightBit::Z80::rr(uint8_t operand) noexcept {
clearBit(NF | HC);
const auto carrying = carry();
setBit(CF, operand & Bit0);
const uint8_t result = (operand >> 1) | (carrying << 7);
clearBit(NF | HC);
adjustXY(result);
return result;
}
@@ -569,8 +572,8 @@ void EightBit::Z80::ccf() noexcept {
}
void EightBit::Z80::cpl() noexcept {
setBit(HC | NF);
IntelProcessor::cpl();
setBit(HC | NF);
adjustXY(A());
}
@@ -624,19 +627,18 @@ void EightBit::Z80::cpd() noexcept {
void EightBit::Z80::cpir() noexcept {
cpi();
if (parity() != 0 && zero() == 0) {
if (parity() != 0 && zero() == 0)
repeatBlockInstruction();
tick(5);
}
}
void EightBit::Z80::cpdr() noexcept {
cpd();
if (parity() != 0 && zero() == 0) {
repeatBlockInstruction();
tick(5);
} else {
MEMPTR() = PC().joined - 2;
MEMPTR() = PC();
--MEMPTR();
--MEMPTR();
tick(2);
}
}
@@ -664,36 +666,39 @@ void EightBit::Z80::ldi() noexcept {
++DE();
}
void EightBit::Z80::ldir() noexcept {
ldi();
void EightBit::Z80::loadRepeat() noexcept {
if (parity() != 0)
repeatBlockInstruction();
tick(5);
else
tick(5);
}
void EightBit::Z80::ldir() noexcept {
ldi();
loadRepeat();
}
void EightBit::Z80::lddr() noexcept {
ldd();
if (parity() != 0)
repeatBlockInstruction();
tick(5);
loadRepeat();
}
void EightBit::Z80::repeatBlockInstruction() noexcept {
MEMPTR() = --PC();
--PC();
adjustXY(PC().high);
tick(5);
}
void EightBit::Z80::adjustBlockRepeatFlagsIO() noexcept {
auto direction = B();
if (carry() != 0) {
const auto negative = signTest(BUS().DATA()) != 0;
const uint8_t direction = B() + (negative ? -1 : +1);
const auto calculatedParity = (parity() >> 2) ^ (evenParity(direction & Mask::Mask3) ? 1 : 0) ^ 1;
setBit(PF, calculatedParity != 0);
direction += (negative ? -1 : +1);
setBit(HC, (B() & Mask::Mask4) == (negative ? 0 : Mask::Mask4));
} else {
adjustParity((B() & Mask::Mask3));
}
const auto calculatedParity = (parity() >> 2) ^ (evenParity(direction & Mask::Mask3) ? 1 : 0) ^ 1;
setBit(PF, calculatedParity != 0);
}
void EightBit::Z80::adjustBlockInputOutputFlags(int basis) noexcept {
@@ -743,7 +748,6 @@ void EightBit::Z80::inir() noexcept {
if (zero() == 0) {
repeatBlockInstruction();
adjustBlockRepeatFlagsIO();
tick(5);
}
}
@@ -752,7 +756,6 @@ void EightBit::Z80::indr() noexcept {
if (zero() == 0) {
repeatBlockInstruction();
adjustBlockRepeatFlagsIO();
tick(5);
}
}
@@ -782,7 +785,6 @@ void EightBit::Z80::otir() noexcept {
if (zero() == 0) {
repeatBlockInstruction();
adjustBlockRepeatFlagsIO();
tick(5);
}
}
@@ -791,29 +793,26 @@ void EightBit::Z80::otdr() noexcept {
if (zero() == 0) {
repeatBlockInstruction();
adjustBlockRepeatFlagsIO();
tick(5);
}
}
void EightBit::Z80::rrd(register16_t address, uint8_t& update) noexcept {
MEMPTR() = BUS().ADDRESS() = address;
++MEMPTR();
const auto memory = memoryRead();
tick(4);
IntelProcessor::memoryWrite(promoteNibble(update) | highNibble(memory));
update = higherNibble(update) | lowerNibble(memory);
adjustSZPXY(update);
void EightBit::Z80::rrd() noexcept {
readMemoryIndirect(HL());
const auto memory = BUS().DATA();
BUS().DATA() = promoteNibble(A()) | highNibble(memory);
memoryUpdate(5);
A() = higherNibble(A()) | lowerNibble(memory);
adjustSZPXY(A());
clearBit(NF | HC);
}
void EightBit::Z80::rld(register16_t address, uint8_t& update) noexcept {
MEMPTR() = BUS().ADDRESS() = address;
++MEMPTR();
const auto memory = memoryRead();
tick(4);
IntelProcessor::memoryWrite(promoteNibble(memory) | lowNibble(update));
update = higherNibble(update) | highNibble(memory);
adjustSZPXY(update);
void EightBit::Z80::rld() noexcept {
readMemoryIndirect(HL());
const auto memory = BUS().DATA();
BUS().DATA() = promoteNibble(memory) | lowNibble(A());
memoryUpdate(5);
A() = higherNibble(A()) | highNibble(memory);
adjustSZPXY(A());
clearBit(NF | HC);
}
@@ -1255,10 +1254,10 @@ void EightBit::Z80::executeED(const int x, const int y, const int z, const int p
readInternalRegister([this]() { return REFRESH(); });
break;
case 4: // RRD
rrd(HL(), A());
rrd();
break;
case 5: // RLD
rld(HL(), A());
rld();
break;
case 6: // NOP
case 7: // NOP
@@ -1665,16 +1664,13 @@ void EightBit::Z80::executeOther(const int x, const int y, const int z, const in
callIndirect();
break;
case 1: // DD prefix
m_prefixDD = true;
IntelProcessor::execute(fetchInstruction());
prefixDD();
break;
case 2: // ED prefix
m_prefixED = true;
IntelProcessor::execute(fetchInstruction());
prefixED();
break;
case 3: // FD prefix
m_prefixFD = true;
IntelProcessor::execute(fetchInstruction());
prefixFD();
break;
default:
UNREACHABLE;
@@ -1746,6 +1742,21 @@ void EightBit::Z80::pushRegisterPair(int p) {
pushShort(RP2(p));
}
void EightBit::Z80::prefixDD() {
m_prefixDD = true;
IntelProcessor::execute(fetchInstruction());
}
void EightBit::Z80::prefixED() {
m_prefixED = true;
IntelProcessor::execute(fetchInstruction());
}
void EightBit::Z80::prefixFD() {
m_prefixFD = true;
IntelProcessor::execute(fetchInstruction());
}
void EightBit::Z80::prefixCB() {
m_prefixCB = true;
if (displaced()) {
+1
View File
@@ -154,6 +154,7 @@ namespace EightBit {
virtual void jumpIndirect() noexcept;
void jump() noexcept;
void callIndirect() noexcept;
using Processor::call;
void call() noexcept;
virtual void jumpRelative(int8_t offset) noexcept;
void jumpRelative(uint8_t offset) noexcept { jumpRelative(static_cast<int8_t>(offset)); }
+2 -2
View File
@@ -64,11 +64,11 @@ namespace EightBit {
void memoryWrite(register16_t address) noexcept;
void memoryWrite(uint8_t data) noexcept;
virtual void memoryWrite() noexcept;
virtual void busWrite() noexcept;
void busWrite() noexcept;
uint8_t memoryRead(register16_t address) noexcept;
virtual uint8_t memoryRead() noexcept;
virtual uint8_t busRead() noexcept;
uint8_t busRead() noexcept;
virtual register16_t& incrementPC() noexcept;
-100
View File
@@ -28,103 +28,3 @@ namespace EightBit {
}
};
}
//
//
//#pragma once
//
//#include <cstring>
//#include <type_traits>
//#include <vector>
//#include "EventArgs.h"
//
//namespace EightBit {
// template<class T> class Signal final {
// private:
// using fn_t = void(*)(void*, T&);
// using dtor_t = void(*)(void*);
//
// struct Entry {
// void* ctx;
// fn_t fn;
// dtor_t dtor; // null for inline-stored (trivially-destructible) callables
// };
//
// int m_count = 0;
// std::vector<Entry> m_delegates;
//
// void destroy_entries() noexcept {
// for (auto& e : m_delegates)
// if (e.dtor) e.dtor(e.ctx);
// m_delegates.clear();
// m_count = 0;
// }
//
// public:
// Signal() = default;
//
// // Copies are connection-less: a copied chip instance starts with no listeners
// Signal(const Signal&) noexcept {}
// Signal& operator=(const Signal&) noexcept { destroy_entries(); return *this; }
//
// ~Signal() { destroy_entries(); }
//
// template<typename Callable>
// void connect(Callable&& callable) {
// using Decay = std::decay_t<Callable>;
//
// // [this] lambdas on x64: sizeof == 8 == sizeof(void*), trivially copyable/destructible.
// // Pack the captured pointer directly into the void* slot — zero heap overhead,
// // and the dispatch function knows the concrete type so c(e) can be inlined.
// constexpr bool fits_inline =
// sizeof(Decay) <= sizeof(void*) &&
// alignof(Decay) <= alignof(void*) &&
// std::is_trivially_copyable_v<Decay> &&
// std::is_trivially_destructible_v<Decay>;
//
// if constexpr (fits_inline) {
// void* storage = nullptr;
// std::memcpy(&storage, &callable, sizeof(Decay));
// m_delegates.push_back({
// storage,
// [](void* raw, T& e) noexcept {
// // Reconstruct the callable from the packed pointer bits.
// // Trivially-copyable, so memcpy into aligned storage is valid.
// alignas(Decay) char buf[sizeof(Decay)];
// std::memcpy(buf, &raw, sizeof(Decay));
// (*reinterpret_cast<Decay*>(buf))(e);
// },
// nullptr
// });
// }
// else {
// auto* heap = new Decay(std::forward<Callable>(callable));
// m_delegates.push_back({
// static_cast<void*>(heap),
// [](void* raw, T& e) noexcept {
// (*static_cast<Decay*>(raw))(e);
// },
// [](void* raw) noexcept {
// delete static_cast<Decay*>(raw);
// }
// });
// }
// ++m_count;
// }
//
// [[nodiscard]] constexpr auto count() const noexcept { return m_count; }
// [[nodiscard]] constexpr auto inactive() const noexcept { return count() == 0; }
// [[nodiscard]] constexpr auto singular() const noexcept { return count() == 1; }
// [[nodiscard]] constexpr auto active() const noexcept { return count() != 0; }
//
// void fire(T& e = EventArgs::empty()) const noexcept {
// //if (inactive()) return;
// //if (singular()) {
// // m_delegates[0].fn(m_delegates[0].ctx, e);
// // return;
// //}
// for (const auto& entry : m_delegates)
// entry.fn(entry.ctx, e);
// }
// };
//}
+1 -1
View File
@@ -123,7 +123,7 @@ void EightBit::IntelProcessor::callIndirect() noexcept {
}
void EightBit::IntelProcessor::call() noexcept {
LittleEndianProcessor::call(MEMPTR());
call(MEMPTR());
}
void EightBit::IntelProcessor::jumpConditionalFlag(int flag) noexcept {