diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index afce1bf6c..6db645a89 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -917,6 +917,7 @@ 4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; }; 4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; }; 4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; + 4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */; }; 4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; }; 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; }; 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; @@ -1934,6 +1935,7 @@ 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = ""; }; 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = ""; }; 4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = ""; }; + 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Z80ContentionTests.mm; sourceTree = ""; }; 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = ""; }; 4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = ""; }; 4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; @@ -3964,6 +3966,7 @@ 4B2AF8681E513FC20027EE29 /* TIATests.mm */, 4B1D08051E0F7A1100763741 /* TimeTests.mm */, 4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */, + 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */, 4BB73EB81B587A5100552FC2 /* Info.plist */, 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */, 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */, @@ -5649,6 +5652,7 @@ 4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */, 4B778F5E23A5F3230000D260 /* Oric.cpp in Sources */, 4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */, + 4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */, 4B778F1A23A5ED320000D260 /* Video.cpp in Sources */, 4B778F3B23A5F1650000D260 /* KeyboardMachine.cpp in Sources */, 4B778F2E23A5F09E0000D260 /* IRQDelegatePortHandler.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm new file mode 100644 index 000000000..2c03cb83e --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -0,0 +1,1472 @@ +// +// Z80ContentionTests.cpp +// Clock SignalTests +// +// Created by Thomas Harte on 7/4/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#import + +#include "../../../Processors/Z80/Z80.hpp" + +namespace { + +static constexpr uint16_t initial_pc = 0x0000; +static constexpr uint16_t initial_ir = 0xe000; +static constexpr uint16_t initial_bc_de_hl = 0xabcd; +static constexpr uint16_t initial_ix_iy = 0x3412; +static constexpr uint16_t initial_sp = 0x6800; + +struct CapturingZ80: public CPU::Z80::BusHandler { + + template CapturingZ80(const Collection &code) : z80_(*this) { + // Take a copy of the code. + std::copy(code.begin(), code.end(), ram_); + code_length_ = uint16_t(code.size()); + + // Skip the three cycles the Z80 spends on a reset, and + // purge them from the record. + run_for(3); + bus_records_.clear(); + + // Set the flags so that if this is a conditional operation, it'll succeed. + if((*code.begin())&0x8) { + z80_.set_value_of_register(CPU::Z80::Register::Flags, 0xff); + } else { + z80_.set_value_of_register(CPU::Z80::Register::Flags, 0x00); + } + + // Set the refresh address to the EE page and set A to 0x80. + z80_.set_value_of_register(CPU::Z80::Register::I, 0xe0); + z80_.set_value_of_register(CPU::Z80::Register::A, 0x80); + + // Set BC, DE and HL. + z80_.set_value_of_register(CPU::Z80::Register::BC, initial_bc_de_hl); + z80_.set_value_of_register(CPU::Z80::Register::DE, initial_bc_de_hl); + z80_.set_value_of_register(CPU::Z80::Register::HL, initial_bc_de_hl); + + // Set IX and IY. + z80_.set_value_of_register(CPU::Z80::Register::IX, initial_ix_iy); + z80_.set_value_of_register(CPU::Z80::Register::IY, initial_ix_iy); + + // Set SP. + z80_.set_value_of_register(CPU::Z80::Register::StackPointer, initial_sp); + } + + void set_de(uint16_t value) { + z80_.set_value_of_register(CPU::Z80::Register::DE, value); + } + + void set_bc(uint16_t value) { + z80_.set_value_of_register(CPU::Z80::Register::BC, value); + } + + void run_for(int cycles) { + z80_.run_for(HalfCycles(Cycles(cycles))); + XCTAssertEqual(bus_records_.size(), cycles * 2); + } + + /// A record of the state of the address bus, MREQ, IOREQ and RFSH lines, + /// upon every clock transition. + struct BusRecord { + uint16_t address = 0xffff; + bool mreq = false, ioreq = false, refresh = false; + }; + + HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + // Log the activity. + const uint8_t *const bus_state = cycle.bus_state(); + for(int c = 0; c < cycle.length.as(); c++) { + bus_records_.emplace_back(); + + // TODO: I think everything tested here should have an address, + // but am currently unsure whether the reset program puts the + // address bus in high impedance, as bus req/ack does. + if(cycle.address) { + bus_records_.back().address = *cycle.address; + } + bus_records_.back().mreq = bus_state[c] & CPU::Z80::PartialMachineCycle::Line::MREQ; + bus_records_.back().ioreq = bus_state[c] & CPU::Z80::PartialMachineCycle::Line::IOREQ; + bus_records_.back().refresh = bus_state[c] & CPU::Z80::PartialMachineCycle::Line::RFSH; + } + + // Provide only reads. + if( + cycle.operation == CPU::Z80::PartialMachineCycle::Read || + cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode + ) { + *cycle.value = ram_[*cycle.address]; + + XCTAssert(cycle.operation != CPU::Z80::PartialMachineCycle::ReadOpcode || *cycle.address < code_length_); + } + + return HalfCycles(0); + } + + const std::vector &bus_records() const { + return bus_records_; + } + + std::vector cycle_records() const { + std::vector cycle_records; + for(size_t c = 0; c < bus_records_.size(); c += 2) { + cycle_records.push_back(bus_records_[c]); + } + return cycle_records; + } + + private: + CPU::Z80::Processor z80_; + uint8_t ram_[65536]; + uint16_t code_length_ = 0; + + std::vector bus_records_; +}; + +} + +@interface Z80ContentionTests : XCTestCase +@end + +/*! + Tests the Z80's MREQ, IOREQ and address outputs for correlation to those + observed by ZX Spectrum users in the software-side documentation of + contended memory timings. +*/ +@implementation Z80ContentionTests + +struct ContentionCheck { + uint16_t address; + int length; + bool is_io = false; +}; + +- (void)compareExpectedContentions:(const std::initializer_list &)contentions found:(const std::vector &)found label:(const char *)label { + XCTAssertEqual(contentions.size(), found.size(), "[%s] found %lu contentions but expected %zu", label, found.size(), contentions.size()); + + auto contention = contentions.begin(); + auto found_contention = found.begin(); + + while(contention != contentions.end() && found_contention != found.end()) { + XCTAssertEqual(contention->address, found_contention->address, "[%s] mismatched address at step %zu; expected %04x but found %04x", label, contention - contentions.begin(), contention->address, found_contention->address); + XCTAssertEqual(contention->length, found_contention->length, "[%s] mismatched length at step %zu; expected %d but found %d", label, contention - contentions.begin(), contention->length, found_contention->length); + XCTAssertEqual(contention->is_io, found_contention->is_io, "[%s] mismatched IO flag at step %zu; expected %d but found %d", label, contention - contentions.begin(), contention->is_io, found_contention->is_io); + + if(contention->address != found_contention->address || contention->length != found_contention->length) { + break; + } + + ++contention; + ++found_contention; + } +} + +/*! + Checks that the accumulated bus activity in @c z80 matches the expectations given in @c contentions if + processed by a Sinclair 48k or 128k ULA. +*/ +- (void)validate48Contention:(const std::initializer_list &)contentions z80:(const CapturingZ80 &)z80 { + // 48[/128]k contention logic: triggered on address alone, _unless_ + // MREQ is also active. + const auto bus_records = z80.cycle_records(); + std::vector found_contentions; + + int count = 0; + uint16_t address = bus_records.front().address; + bool is_io = false; + + for(size_t c = 0; c < bus_records.size(); c++) { + ++count; + + if( + c && // i.e. not at front. + !bus_records[c].mreq && // i.e. beginning of a new contention. + !bus_records[c].ioreq // i.e. not during an IO cycle. + ) { + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count - 1; + found_contentions.back().is_io = is_io; + + count = 1; + address = bus_records[c].address; + } + + is_io = bus_records[c].ioreq; + } + + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count; + found_contentions.back().is_io = is_io; + + [self compareExpectedContentions:contentions found:found_contentions label:"48kb"]; +} + +/*! + Checks that the accumulated bus activity in @c z80 matches the expectations given in @c contentions if + processed by an Amstrad gate array. +*/ +- (void)validatePlus3Contention:(const std::initializer_list &)contentions z80:(const CapturingZ80 &)z80 { + // +3 contention logic: triggered by the leading edge of MREQ, sans refresh. + const auto bus_records = z80.bus_records(); + std::vector found_contentions; + + int count = 0; + uint16_t address = bus_records.front().address; + bool is_io = false; + + for(size_t c = 0; c < bus_records.size(); c += 2) { + ++count; + + // The IOREQ test below is a little inauthentic; it's included to match the published Spectrum + // tables, even though the +3 doesn't contend IO. + const bool is_mreq_leading_edge = !bus_records[c].mreq && bus_records[c+1].mreq && !bus_records[c].refresh; + const bool is_ioreq_leading_edge = c < bus_records.size() - 2 && !bus_records[c].ioreq && bus_records[c+2].ioreq; + if( + c && // i.e. not at front. + (is_mreq_leading_edge || is_ioreq_leading_edge) // i.e. beginning of a new contention. + ) { + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count - 1; + found_contentions.back().is_io = is_io; + + count = 1; + address = bus_records[c].address; + } + + is_io = bus_records[c].ioreq; + } + + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count; + found_contentions.back().is_io = is_io; + + [self compareExpectedContentions:contentions found:found_contentions label:"+3"]; +} + +// MARK: - Opcode tests. + +- (void)testSimpleOneBytes { + for(uint8_t opcode : { + 0x00, // NOP + + // LD r, r'. + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, + + // ALO a, r + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbf, + + // INC/DEC r + 0x04, 0x05, 0x0c, 0x0d, + 0x14, 0x15, 0x1c, 0x1d, + 0x24, 0x25, 0x2c, 0x2d, + + 0xd9, // EXX + 0x08, // EX AF, AF' + 0xeb, // EX DE, HL + 0x27, // DAA + 0x2f, // CPL + 0x3f, // CCF + 0x37, // SCF + 0xf3, // DI + 0xfb, // EI + 0x17, // RLA + 0x1f, // RRA + 0x07, // RLCA + 0x0f, // RRCA + 0xe9, // JP (HL) + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(4); + + [self validate48Contention:{{initial_pc, 4}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}} z80:z80]; + } +} + +- (void)testSimpleTwoBytes { + // This group should apparently also include 'NOPD'. Neither I nor any other + // page I could find seems to have heard of 'NOPD'. + + for(const auto &sequence : std::vector>{ + // SRO d (i.e. RLC, RRC, RL, RR, SLA, SRA, SRL and SLL) + {0xcb, 0x00}, {0xcb, 0x01}, {0xcb, 0x02}, {0xcb, 0x03}, + {0xcb, 0x04}, {0xcb, 0x05}, {0xcb, 0x07}, + {0xcb, 0x08}, {0xcb, 0x09}, {0xcb, 0x0a}, {0xcb, 0x0b}, + {0xcb, 0x0c}, {0xcb, 0x0d}, {0xcb, 0x0f}, + {0xcb, 0x10}, {0xcb, 0x11}, {0xcb, 0x12}, {0xcb, 0x13}, + {0xcb, 0x14}, {0xcb, 0x15}, {0xcb, 0x17}, + {0xcb, 0x18}, {0xcb, 0x19}, {0xcb, 0x1a}, {0xcb, 0x1b}, + {0xcb, 0x1c}, {0xcb, 0x1d}, {0xcb, 0x1f}, + {0xcb, 0x20}, {0xcb, 0x21}, {0xcb, 0x22}, {0xcb, 0x23}, + {0xcb, 0x24}, {0xcb, 0x25}, {0xcb, 0x27}, + {0xcb, 0x28}, {0xcb, 0x29}, {0xcb, 0x2a}, {0xcb, 0x2b}, + {0xcb, 0x2c}, {0xcb, 0x2d}, {0xcb, 0x2f}, + {0xcb, 0x30}, {0xcb, 0x31}, {0xcb, 0x32}, {0xcb, 0x33}, + {0xcb, 0x34}, {0xcb, 0x35}, {0xcb, 0x37}, + {0xcb, 0x38}, {0xcb, 0x39}, {0xcb, 0x3a}, {0xcb, 0x3b}, + {0xcb, 0x3c}, {0xcb, 0x3d}, {0xcb, 0x3f}, + + // BIT b, r + {0xcb, 0x40}, {0xcb, 0x41}, {0xcb, 0x42}, {0xcb, 0x43}, + {0xcb, 0x44}, {0xcb, 0x45}, {0xcb, 0x47}, + {0xcb, 0x48}, {0xcb, 0x49}, {0xcb, 0x4a}, {0xcb, 0x4b}, + {0xcb, 0x4c}, {0xcb, 0x4d}, {0xcb, 0x4f}, + {0xcb, 0x50}, {0xcb, 0x51}, {0xcb, 0x52}, {0xcb, 0x53}, + {0xcb, 0x54}, {0xcb, 0x55}, {0xcb, 0x57}, + {0xcb, 0x58}, {0xcb, 0x59}, {0xcb, 0x5a}, {0xcb, 0x5b}, + {0xcb, 0x5c}, {0xcb, 0x5d}, {0xcb, 0x5f}, + {0xcb, 0x60}, {0xcb, 0x61}, {0xcb, 0x62}, {0xcb, 0x63}, + {0xcb, 0x64}, {0xcb, 0x65}, {0xcb, 0x67}, + {0xcb, 0x68}, {0xcb, 0x69}, {0xcb, 0x6a}, {0xcb, 0x6b}, + {0xcb, 0x6c}, {0xcb, 0x6d}, {0xcb, 0x6f}, + {0xcb, 0x70}, {0xcb, 0x71}, {0xcb, 0x72}, {0xcb, 0x73}, + {0xcb, 0x74}, {0xcb, 0x75}, {0xcb, 0x77}, + {0xcb, 0x78}, {0xcb, 0x79}, {0xcb, 0x7a}, {0xcb, 0x7b}, + {0xcb, 0x7c}, {0xcb, 0x7d}, {0xcb, 0x7f}, + + // RES b, r + {0xcb, 0x80}, {0xcb, 0x81}, {0xcb, 0x82}, {0xcb, 0x83}, + {0xcb, 0x84}, {0xcb, 0x85}, {0xcb, 0x87}, + {0xcb, 0x88}, {0xcb, 0x89}, {0xcb, 0x8a}, {0xcb, 0x8b}, + {0xcb, 0x8c}, {0xcb, 0x8d}, {0xcb, 0x8f}, + {0xcb, 0x90}, {0xcb, 0x91}, {0xcb, 0x92}, {0xcb, 0x93}, + {0xcb, 0x94}, {0xcb, 0x95}, {0xcb, 0x97}, + {0xcb, 0x98}, {0xcb, 0x99}, {0xcb, 0x9a}, {0xcb, 0x9b}, + {0xcb, 0x9c}, {0xcb, 0x9d}, {0xcb, 0x9f}, + {0xcb, 0xa0}, {0xcb, 0xa1}, {0xcb, 0xa2}, {0xcb, 0xa3}, + {0xcb, 0xa4}, {0xcb, 0xa5}, {0xcb, 0xa7}, + {0xcb, 0xa8}, {0xcb, 0xa9}, {0xcb, 0xaa}, {0xcb, 0xab}, + {0xcb, 0xac}, {0xcb, 0xad}, {0xcb, 0xaf}, + {0xcb, 0xb0}, {0xcb, 0xb1}, {0xcb, 0xb2}, {0xcb, 0xb3}, + {0xcb, 0xb4}, {0xcb, 0xb5}, {0xcb, 0xb7}, + {0xcb, 0xb8}, {0xcb, 0xb9}, {0xcb, 0xba}, {0xcb, 0xbb}, + {0xcb, 0xbc}, {0xcb, 0xbd}, {0xcb, 0xbf}, + + // SET b, r + {0xcb, 0xc0}, {0xcb, 0xc1}, {0xcb, 0xc2}, {0xcb, 0xc3}, + {0xcb, 0xc4}, {0xcb, 0xc5}, {0xcb, 0xc7}, + {0xcb, 0xc8}, {0xcb, 0xc9}, {0xcb, 0xca}, {0xcb, 0xcb}, + {0xcb, 0xcc}, {0xcb, 0xcd}, {0xcb, 0xcf}, + {0xcb, 0xc0}, {0xcb, 0xd1}, {0xcb, 0xd2}, {0xcb, 0xd3}, + {0xcb, 0xd4}, {0xcb, 0xd5}, {0xcb, 0xd7}, + {0xcb, 0xd8}, {0xcb, 0xd9}, {0xcb, 0xda}, {0xcb, 0xdb}, + {0xcb, 0xdc}, {0xcb, 0xdd}, {0xcb, 0xdf}, + {0xcb, 0xe0}, {0xcb, 0xe1}, {0xcb, 0xe2}, {0xcb, 0xe3}, + {0xcb, 0xe4}, {0xcb, 0xe5}, {0xcb, 0xe7}, + {0xcb, 0xe8}, {0xcb, 0xe9}, {0xcb, 0xea}, {0xcb, 0xeb}, + {0xcb, 0xec}, {0xcb, 0xed}, {0xcb, 0xef}, + {0xcb, 0xf0}, {0xcb, 0xf1}, {0xcb, 0xf2}, {0xcb, 0xf3}, + {0xcb, 0xf4}, {0xcb, 0xf5}, {0xcb, 0xf7}, + {0xcb, 0xf8}, {0xcb, 0xf9}, {0xcb, 0xfa}, {0xcb, 0xfb}, + {0xcb, 0xfc}, {0xcb, 0xfd}, {0xcb, 0xff}, + + // NEG + {0xed, 0x44}, {0xed, 0x4c}, {0xed, 0x54}, {0xed, 0x5c}, + {0xed, 0x64}, {0xed, 0x6c}, {0xed, 0x74}, {0xed, 0x7c}, + + // IM 0/1/2 + {0xed, 0x46}, {0xed, 0x4e}, {0xed, 0x56}, {0xed, 0x5e}, + {0xed, 0x66}, {0xed, 0x6e}, {0xed, 0x66}, {0xed, 0x6e}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(8); + + [self validate48Contention:{{initial_pc, 4}, {initial_pc+1, 4}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 4}} z80:z80]; + } +} + +- (void)testAIR { + for(const auto &sequence : std::vector>{ + {0xed, 0x57}, // LD A, I + {0xed, 0x5f}, // LD A, R + {0xed, 0x47}, // LD I, A + {0xed, 0x4f}, // LD R, A + }) { + CapturingZ80 z80(sequence); + z80.run_for(9); + + [self validate48Contention:{{initial_pc, 4}, {initial_pc+1, 4}, {initial_ir+1, 1}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 5}} z80:z80]; + } +} + +- (void)testINCDEC16 { + for(uint8_t opcode : { + // INC dd + 0x03, 0x13, 0x23, 0x33, + + // DEC dd + 0x0b, 0x1b, 0x2b, 0x3b, + + // LD SP, HL + 0xf9, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(6); + + [self validate48Contention:{{initial_pc, 4}, {initial_ir, 1}, {initial_ir, 1}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 6}} z80:z80]; + } +} + +- (void)testADDHLdd { + for(uint8_t opcode : { + // ADD hl, dd + 0x09, 0x19, 0x29, 0x39, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(11); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_ir, 1}, + {initial_ir, 1}, + {initial_ir, 1}, + {initial_ir, 1}, + {initial_ir, 1}, + {initial_ir, 1}, + {initial_ir, 1}, + } z80:z80]; + [self validatePlus3Contention:{{initial_pc, 11}} z80:z80]; + } +} + +- (void)testADCSBCHLdd { + for(const auto &sequence : std::vector>{ + // ADC HL, dd + {0xed, 0x4a}, {0xed, 0x5a}, {0xed, 0x6a}, {0xed, 0x7a}, + + // SBC HL, dd + {0xed, 0x42}, {0xed, 0x52}, {0xed, 0x62}, {0xed, 0x72}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(15); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_ir+1, 1}, + {initial_ir+1, 1}, + {initial_ir+1, 1}, + {initial_ir+1, 1}, + {initial_ir+1, 1}, + {initial_ir+1, 1}, + {initial_ir+1, 1}, + } z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 11}} z80:z80]; + } +} + +- (void)testLDrnALOAn { + for(uint8_t opcode : { + // LD r, n + 0x06, 0x0e, 0x16, 0x1e, 0x26, 0x2e, 0x3e, + + 0xc6, // ADD A, n + 0xce, // ADC A, n + 0xde, // SBC A, n + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(7); + + [self validate48Contention:{{initial_pc, 4}, {initial_pc+1, 3}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 3}} z80:z80]; + } +} + +- (void)testLDrind { + for(uint8_t opcode : { + // LD r, (dd) + 0x0a, 0x1a, + 0x46, 0x4e, 0x56, 0x5e, 0x66, 0x6e, 0x7e, 0x86, + + // LD (ss), r + 0x02, 0x12, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x77, + + // ALO A, (HL) + 0x86, 0x8e, 0x96, 0x9e, 0xa6, 0xae, 0xb6, 0xbe + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(7); + + [self validate48Contention:{{initial_pc, 4}, {initial_bc_de_hl, 3}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}, {initial_bc_de_hl, 3}} z80:z80]; + } +} + +- (void)testLDiiPlusn { + constexpr uint8_t offset = 0x10; + for(const auto &sequence : std::vector>{ + // LD r, (ii+n) + {0xdd, 0x46, offset}, {0xdd, 0x4e, offset}, + {0xdd, 0x56, offset}, {0xdd, 0x5e, offset}, + {0xdd, 0x66, offset}, {0xdd, 0x6e, offset}, + + {0xfd, 0x46, offset}, {0xfd, 0x4e, offset}, + {0xfd, 0x56, offset}, {0xfd, 0x5e, offset}, + {0xfd, 0x66, offset}, {0xfd, 0x6e, offset}, + + // LD (ii+n), r + {0xdd, 0x70, offset}, {0xdd, 0x71, offset}, + {0xdd, 0x72, offset}, {0xdd, 0x73, offset}, + {0xdd, 0x74, offset}, {0xdd, 0x75, offset}, + {0xdd, 0x77, offset}, + + {0xfd, 0x70, offset}, {0xfd, 0x71, offset}, + {0xfd, 0x72, offset}, {0xfd, 0x73, offset}, + {0xfd, 0x74, offset}, {0xfd, 0x75, offset}, + {0xfd, 0x77, offset}, + + // ALO A, (ii+n) + {0xdd, 0x86, offset}, {0xdd, 0x8e, offset}, + {0xdd, 0x96, offset}, {0xdd, 0x9e, offset}, + {0xdd, 0xa6, offset}, {0xdd, 0xae, offset}, + {0xdd, 0xb6, offset}, {0xdd, 0xbe, offset}, + + {0xfd, 0x86, offset}, {0xfd, 0x8e, offset}, + {0xfd, 0x96, offset}, {0xfd, 0x9e, offset}, + {0xfd, 0xa6, offset}, {0xfd, 0xae, offset}, + {0xfd, 0xb6, offset}, {0xfd, 0xbe, offset}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(19); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 8}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + } +} + +- (void)testBITbhl { + for(const auto &sequence : std::vector>{ + {0xcb, 0x46}, {0xcb, 0x4e}, + {0xcb, 0x56}, {0xcb, 0x5e}, + {0xcb, 0x66}, {0xcb, 0x6e}, + {0xcb, 0x76}, {0xcb, 0x7e}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(12); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 4}, + } z80:z80]; + } +} + +- (void)testBITbiin { + constexpr uint8_t offset = 0x10; + for(const auto &sequence : std::vector>{ + // BIT b, (ix+d) + {0xdd, 0xcb, offset, 0x46}, {0xdd, 0xcb, offset, 0x4e}, + {0xdd, 0xcb, offset, 0x56}, {0xdd, 0xcb, offset, 0x5e}, + {0xdd, 0xcb, offset, 0x66}, {0xdd, 0xcb, offset, 0x6e}, + {0xdd, 0xcb, offset, 0x76}, {0xdd, 0xcb, offset, 0x7e}, + + // BIT b, (iy+d) + {0xfd, 0xcb, offset, 0x46}, {0xfd, 0xcb, offset, 0x4e}, + {0xfd, 0xcb, offset, 0x56}, {0xfd, 0xcb, offset, 0x5e}, + {0xfd, 0xcb, offset, 0x66}, {0xfd, 0xcb, offset, 0x6e}, + {0xfd, 0xcb, offset, 0x76}, {0xfd, 0xcb, offset, 0x7e}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(20); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 3}, + {initial_pc+3, 1}, + {initial_pc+3, 1}, + {initial_ix_iy + offset, 3}, + {initial_ix_iy + offset, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 5}, + {initial_ix_iy + offset, 4}, + } z80:z80]; + } +} + +- (void)testLDJPJRnn { + for(uint8_t opcode : { + // LD rr, dd + 0x01, 0x11, 0x21, 0x31, + + // JP nn, JP cc, nn + 0xc2, 0xc3, 0xd2, 0xe2, 0xf2, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(10); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + } z80:z80]; + } +} + +- (void)testLDindHLn { + for(uint8_t opcode : { + // LD (HL), n + 0x36 + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(10); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_bc_de_hl, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_bc_de_hl, 3}, + } z80:z80]; + } +} + +- (void)testLDiiPlusnn { + constexpr uint8_t offset = 0x10; + for(const auto &sequence : std::vector>{ + // LD (ii+n), n + {0xdd, 0x36, offset}, {0xfd, 0x36, offset}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(19); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 3}, + {initial_pc+3, 1}, + {initial_pc+3, 1}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 5}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + } +} + +- (void)testLDAind { + for(const auto &sequence : std::vector>{ + {0x32, 0xcd, 0xab}, // LD (nn), a + {0x3a, 0xcd, 0xab}, // LD a, (nn) + }) { + CapturingZ80 z80(sequence); + z80.run_for(13); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + {0xabcd, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + {0xabcd, 3}, + } z80:z80]; + } +} + +- (void)testLDHLind { + for(const auto &sequence : std::vector>{ + {0x22, 0xcd, 0xab}, // LD (nn), HL + {0x2a, 0xcd, 0xab}, // LD HL, (nn) + }) { + CapturingZ80 z80(sequence); + z80.run_for(16); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + {0xabcd, 3}, + {0xabce, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + {0xabcd, 3}, + {0xabce, 3}, + } z80:z80]; + } +} + +- (void)testLDrrind { + for(const auto &sequence : std::vector>{ + {0xed, 0x43, 0xcd, 0xab}, // LD (nn), BC + {0xed, 0x53, 0xcd, 0xab}, // LD (nn), DE + {0xed, 0x63, 0xcd, 0xab}, // LD (nn), HL + {0xed, 0x73, 0xcd, 0xab}, // LD (nn), SP + + {0xed, 0x4b, 0xcd, 0xab}, // LD BC, (nn) + {0xed, 0x5b, 0xcd, 0xab}, // LD DE, (nn) + {0xed, 0x6b, 0xcd, 0xab}, // LD HL, (nn) + {0xed, 0x7b, 0xcd, 0xab}, // LD SP, (nn) + }) { + CapturingZ80 z80(sequence); + z80.run_for(20); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 3}, + {0xabcd, 3}, + {0xabce, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 3}, + {0xabcd, 3}, + {0xabce, 3}, + } z80:z80]; + } +} + +- (void)testINCDECHL { + for(uint8_t opcode : { + 0x34, // INC (HL) + 0x35, // DEC (HL) + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(11); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_bc_de_hl, 4}, + {initial_bc_de_hl, 3}, + } z80:z80]; + } +} + +- (void)testSETRESbHLind { + for(const auto &sequence : std::vector>{ + // SET b, (HL) + {0xcb, 0xc6}, {0xcb, 0xce}, {0xcb, 0xd6}, {0xcb, 0xde}, + {0xcb, 0xe6}, {0xcb, 0xee}, {0xcb, 0xf6}, {0xcb, 0xfe}, + + // RES b, (HL) + {0xcb, 0x86}, {0xcb, 0x8e}, {0xcb, 0x96}, {0xcb, 0x9e}, + {0xcb, 0xa6}, {0xcb, 0xae}, {0xcb, 0xb6}, {0xcb, 0xbe}, + + // SRO (HL) + {0xcb, 0x06}, {0xcb, 0x0e}, {0xcb, 0x16}, {0xcb, 0x1e}, + {0xcb, 0x26}, {0xcb, 0x2e}, {0xcb, 0x36}, {0xcb, 0x3e}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(15); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 4}, + {initial_bc_de_hl, 3}, + } z80:z80]; + } +} + +- (void)testINCDECiin { + constexpr uint8_t offset = 0x10; + for(const auto &sequence : std::vector>{ + // INC (ii+n) + {0xdd, 0x34, offset}, {0xfd, 0x34, offset}, + + // DEC (ii+n) + {0xdd, 0x35, offset}, {0xfd, 0x35, offset}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(23); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_pc+2, 1}, + {initial_ix_iy + offset, 3}, + {initial_ix_iy + offset, 1}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 8}, + {initial_ix_iy + offset, 4}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + } +} + +- (void)testSETRESiin { + constexpr uint8_t offset = 0x10; + for(const auto &sequence : std::vector>{ + // SET b, (ii+n) + {0xdd, 0xcb, offset, 0xc6}, {0xdd, 0xcb, offset, 0xce}, + {0xdd, 0xcb, offset, 0xd6}, {0xdd, 0xcb, offset, 0xde}, + {0xdd, 0xcb, offset, 0xe6}, {0xdd, 0xcb, offset, 0xee}, + {0xdd, 0xcb, offset, 0xf6}, {0xdd, 0xcb, offset, 0xfe}, + + {0xfd, 0xcb, offset, 0xc6}, {0xfd, 0xcb, offset, 0xce}, + {0xfd, 0xcb, offset, 0xd6}, {0xfd, 0xcb, offset, 0xde}, + {0xfd, 0xcb, offset, 0xe6}, {0xfd, 0xcb, offset, 0xee}, + {0xfd, 0xcb, offset, 0xf6}, {0xfd, 0xcb, offset, 0xfe}, + + // RES b, (ii+n) + {0xdd, 0xcb, offset, 0x86}, {0xdd, 0xcb, offset, 0x8e}, + {0xdd, 0xcb, offset, 0x96}, {0xdd, 0xcb, offset, 0x9e}, + {0xdd, 0xcb, offset, 0xa6}, {0xdd, 0xcb, offset, 0xae}, + {0xdd, 0xcb, offset, 0xb6}, {0xdd, 0xcb, offset, 0xbe}, + + {0xfd, 0xcb, offset, 0x86}, {0xfd, 0xcb, offset, 0x8e}, + {0xfd, 0xcb, offset, 0x96}, {0xfd, 0xcb, offset, 0x9e}, + {0xfd, 0xcb, offset, 0xa6}, {0xfd, 0xcb, offset, 0xae}, + {0xfd, 0xcb, offset, 0xb6}, {0xfd, 0xcb, offset, 0xbe}, + + // SRO (ii+n) + {0xdd, 0xcb, offset, 0x06}, {0xdd, 0xcb, offset, 0x0e}, + {0xdd, 0xcb, offset, 0x16}, {0xdd, 0xcb, offset, 0x1e}, + {0xdd, 0xcb, offset, 0x26}, {0xdd, 0xcb, offset, 0x2e}, + {0xdd, 0xcb, offset, 0x36}, {0xdd, 0xcb, offset, 0x3e}, + + {0xfd, 0xcb, offset, 0x06}, {0xfd, 0xcb, offset, 0x0e}, + {0xfd, 0xcb, offset, 0x16}, {0xfd, 0xcb, offset, 0x1e}, + {0xfd, 0xcb, offset, 0x26}, {0xfd, 0xcb, offset, 0x2e}, + {0xfd, 0xcb, offset, 0x36}, {0xfd, 0xcb, offset, 0x3e}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(23); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 3}, + {initial_pc+3, 1}, + {initial_pc+3, 1}, + {initial_ix_iy + offset, 3}, + {initial_ix_iy + offset, 1}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_pc+2, 3}, + {initial_pc+3, 5}, + {initial_ix_iy + offset, 4}, + {initial_ix_iy + offset, 3}, + } z80:z80]; + } +} + +- (void)testPOPddRET { + for(uint8_t opcode : { + // POP dd + 0xc1, 0xd1, 0xe1, 0xf1, + + // RET + 0xc9, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(10); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_sp, 3}, + {initial_sp+1, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_sp, 3}, + {initial_sp+1, 3}, + } z80:z80]; + } +} + +- (void)testRETIN { + for(const auto &sequence : std::vector>{ + // RETN + {0xed, 0x45}, {0xed, 0x55}, {0xed, 0x5d}, + {0xed, 0x65}, {0xed, 0x6d}, {0xed, 0x75}, {0xed, 0x7d}, + + {0xed, 0x4d}, // RETI + }) { + CapturingZ80 z80(sequence); + z80.run_for(14); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_sp, 3}, + {initial_sp+1, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_sp, 3}, + {initial_sp+1, 3}, + } z80:z80]; + } +} + +- (void)testRETcc { + for(uint8_t opcode : { + 0xc0, 0xc8, 0xd0, 0xd8, + 0xe0, 0xe8, 0xf0, 0xf8, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(11); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_ir, 1}, + {initial_sp, 3}, + {initial_sp+1, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 5}, + {initial_sp, 3}, + {initial_sp+1, 3}, + } z80:z80]; + } +} + +- (void)testPUSHRST { + for(uint8_t opcode : { + // PUSH dd + 0xc5, 0xd5, 0xe5, 0xf5, + + // RST x + 0xc7, 0xcf, 0xd7, 0xdf, 0xe7, 0xef, 0xf7, 0xff, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(11); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_ir, 1}, + {initial_sp-1, 3}, + {initial_sp-2, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 5}, + {initial_sp-1, 3}, + {initial_sp-2, 3}, + } z80:z80]; + } +} + +- (void)testCALL { + for(const auto &sequence : std::vector>{ + // CALL cc + {0xc4, 0x00, 0x00}, {0xcc, 0x00, 0x00}, + {0xd4, 0x00, 0x00}, {0xdc, 0x00, 0x00}, + {0xe4, 0x00, 0x00}, {0xec, 0x00, 0x00}, + {0xf4, 0x00, 0x00}, {0xfc, 0x00, 0x00}, + + {0xcd, 0x00, 0x00}, // CALL + }) { + CapturingZ80 z80(sequence); + z80.run_for(17); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 3}, + {initial_pc+2, 1}, + {initial_sp-1, 3}, + {initial_sp-2, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+2, 4}, + {initial_sp-1, 3}, + {initial_sp-2, 3}, + } z80:z80]; + } +} + +- (void)testJR { + for(const auto &sequence : std::vector>{ + // JR cc + {0x20, 0x00}, {0x28, 0x00}, + {0x30, 0x00}, {0x38, 0x00}, + + {0x18, 0x00}, // JR + }) { + CapturingZ80 z80(sequence); + z80.run_for(12); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 8}, + } z80:z80]; + } +} + +- (void)testDJNZ { + for(const auto &sequence : std::vector>{ + {0x10, 0x00} + }) { + CapturingZ80 z80(sequence); + z80.run_for(13); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_ir, 1}, + {initial_pc+1, 3}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + {initial_pc+1, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 5}, + {initial_pc+1, 8}, + } z80:z80]; + } +} + +- (void)testRLDRRD { + for(const auto &sequence : std::vector>{ + {0xed, 0x6f}, // RLD + {0xed, 0x67}, // RRD + }) { + CapturingZ80 z80(sequence); + z80.run_for(18); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 7}, + {initial_bc_de_hl, 3}, + } z80:z80]; + } +} + +- (void)testINOUTn { + for(const auto &sequence : std::vector>{ + {0xdb, 0xef}, // IN A, (n) + {0xd3, 0xef}, // OUT (n), A + }) { + CapturingZ80 z80(sequence); + z80.run_for(11); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {0x80ef, 4, true}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 3}, + {0x80ef, 4, true}, + } z80:z80]; + } +} + +- (void)testINOUTC { + for(const auto &sequence : std::vector>{ + // IN r, (C) + {0xed, 0x40}, {0xed, 0x48}, {0xed, 0x50}, {0xed, 0x58}, + {0xed, 0x60}, {0xed, 0x68}, {0xed, 0x70}, {0xed, 0x78}, + + // OUT r, (C) + {0xed, 0x41}, {0xed, 0x49}, {0xed, 0x51}, {0xed, 0x59}, + {0xed, 0x61}, {0xed, 0x69}, {0xed, 0x71}, {0xed, 0x79}, + }) { + CapturingZ80 z80(sequence); + z80.run_for(12); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 4, true}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 4, true}, + } z80:z80]; + } +} + +- (void)testEXSPHL { + for(uint8_t opcode : { + 0xe3, + }) { + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); + z80.run_for(19); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_sp, 3}, + {initial_sp+1, 3}, + {initial_sp+1, 1}, + {initial_sp+1, 3}, + {initial_sp, 3}, + {initial_sp, 1}, + {initial_sp, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_sp, 3}, + {initial_sp+1, 4}, + {initial_sp+1, 3}, + {initial_sp, 5}, + } z80:z80]; + } +} + +- (void)testLDILDD { + for(const auto &sequence : std::vector>{ + {0xed, 0xa0}, // LDI + {0xed, 0xa8}, // LDD + }) { + CapturingZ80 z80(sequence); + + // Establish a distinct value for DE. + constexpr uint16_t de = 0x9876; + z80.set_de(de); + + z80.run_for(16); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {de, 3}, + {de, 1}, + {de, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {de, 5}, + } z80:z80]; + } +} + +- (void)testLDIRLDDR { + for(const auto &sequence : std::vector>{ + {0xed, 0xb0}, // LDIR + {0xed, 0xb8}, // LDDR + }) { + CapturingZ80 z80(sequence); + + // Establish a distinct value for DE. + constexpr uint16_t de = 0x9876; + z80.set_de(de); + + z80.run_for(21); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {de, 3}, + {de, 1}, + {de, 1}, + {de, 1}, + {de, 1}, + {de, 1}, + {de, 1}, + {de, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {de, 10}, + } z80:z80]; + } +} + +- (void)testCPICPD { + for(const auto &sequence : std::vector>{ + {0xed, 0xa1}, // CPI + {0xed, 0xa9}, // CPD + }) { + CapturingZ80 z80(sequence); + z80.run_for(16); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 8}, + } z80:z80]; + } +} + +- (void)testCPIRCPDR { + for(const auto &sequence : std::vector>{ + {0xed, 0xb1}, // CPIR + {0xed, 0xb9}, // CPDR + }) { + CapturingZ80 z80(sequence); + z80.run_for(21); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_bc_de_hl, 13}, + } z80:z80]; + } +} + +- (void)testINIIND { + for(const auto &sequence : std::vector>{ + {0xed, 0xa2}, // INI + {0xed, 0xaa}, // IND + }) { + CapturingZ80 z80(sequence); + z80.run_for(16); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_ir+1, 1}, + {initial_bc_de_hl, 4, true}, + {initial_bc_de_hl, 3}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 5}, + {initial_bc_de_hl, 4, true}, + {initial_bc_de_hl, 3}, + } z80:z80]; + } +} + +- (void)testINIRINDR { + for(const auto &sequence : std::vector>{ + {0xed, 0xb2}, // INIR + {0xed, 0xba}, // INDR + }) { + CapturingZ80 z80(sequence); + z80.run_for(21); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_ir+1, 1}, + {initial_bc_de_hl, 4, true}, + {initial_bc_de_hl, 3}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + {initial_bc_de_hl, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 5}, + {initial_bc_de_hl, 4, true}, + {initial_bc_de_hl, 8}, + } z80:z80]; + } +} + +- (void)testOUTIOUTD { + for(const auto &sequence : std::vector>{ + {0xed, 0xa3}, // OUTI + {0xed, 0xab}, // OUTD + }) { + CapturingZ80 z80(sequence); + + // Establish a distinct value for BC. + constexpr uint16_t bc = 0x9876; + z80.set_bc(bc); + + z80.run_for(16); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_ir+1, 1}, + {initial_bc_de_hl, 3}, + {bc - 256, 4, true}, // B is decremented before the output. + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 5}, + {initial_bc_de_hl, 3}, + {bc - 256, 4, true}, + } z80:z80]; + } +} + +- (void)testOTIROTDR { + for(const auto &sequence : std::vector>{ + {0xed, 0xb3}, // OTIR + {0xed, 0xbb}, // OTDR + }) { + CapturingZ80 z80(sequence); + + // Establish a distinct value for BC. + constexpr uint16_t bc = 0x9876; + z80.set_bc(bc); + + z80.run_for(21); + + [self validate48Contention:{ + {initial_pc, 4}, + {initial_pc+1, 4}, + {initial_ir+1, 1}, + {initial_bc_de_hl, 3}, + {bc - 256, 4, true}, // B is decremented before the output. + {bc - 256, 1}, + {bc - 256, 1}, + {bc - 256, 1}, + {bc - 256, 1}, + {bc - 256, 1}, + } z80:z80]; + [self validatePlus3Contention:{ + {initial_pc, 4}, + {initial_pc+1, 5}, + {initial_bc_de_hl, 3}, + {bc - 256, 9, false}, // Abuse of the is_io flag here for the purposes of testing; + // the +3 doesn't contend output so this doesn't matter. + } z80:z80]; + } +} + +@end diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 677d55d69..490e5f50f 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -82,24 +82,25 @@ template < class T, } number_of_cycles_ -= operation->machine_cycle.length; last_request_status_ = request_status_; + + // TODO: eliminate this conditional if all bus cycles have an address filled in. + last_address_bus_ = operation->machine_cycle.address ? *operation->machine_cycle.address : 0xdead; + number_of_cycles_ -= bus_handler_.perform_machine_cycle(operation->machine_cycle); if(uses_bus_request && bus_request_line_) goto do_bus_acknowledge; break; case MicroOp::MoveToNextProgram: advance_operation(); break; - case MicroOp::DecodeOperation: + case MicroOp::IncrementR: refresh_addr_ = ir_; - ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + current_instruction_page_->r_step) & 0x7f); + ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + 1) & 0x7f); + break; + case MicroOp::DecodeOperation: pc_.full += pc_increment_ & uint16_t(halt_mask_); scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_]; flag_adjustment_history_ <<= 1; break; - case MicroOp::DecodeOperationNoRChange: - refresh_addr_ = ir_; - pc_.full += pc_increment_ & uint16_t(halt_mask_); - scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_]; - break; case MicroOp::Increment8NoFlags: ++ *static_cast(operation->source); break; case MicroOp::Increment16: ++ *static_cast(operation->source); break; @@ -927,7 +928,7 @@ template < class T, return wait_line_; } -#define isTerminal(n) (n == MicroOp::MoveToNextProgram || n == MicroOp::DecodeOperation || n == MicroOp::DecodeOperationNoRChange) +#define isTerminal(n) (n == MicroOp::MoveToNextProgram || n == MicroOp::DecodeOperation) template < class T, bool uses_bus_request, diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index b41d8715e..8f2404ed8 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -20,14 +20,14 @@ ProcessorStorage::ProcessorStorage() { #define ReadOpcodeWait(f) PartialMachineCycle(PartialMachineCycle::ReadOpcodeWait, HalfCycles(2), &pc_.full, &operation_, f) #define ReadOpcodeEnd() PartialMachineCycle(PartialMachineCycle::ReadOpcode, HalfCycles(1), &pc_.full, &operation_, false) -#define Refresh(len) PartialMachineCycle(PartialMachineCycle::Refresh, HalfCycles(len), &refresh_addr_.full, nullptr, false) +#define Refresh() PartialMachineCycle(PartialMachineCycle::Refresh, HalfCycles(4), &refresh_addr_.full, nullptr, false) #define ReadStart(addr, val) PartialMachineCycle(PartialMachineCycle::ReadStart, HalfCycles(3), &addr.full, &val, false) -#define ReadWait(l, addr, val, f) PartialMachineCycle(PartialMachineCycle::ReadWait, HalfCycles(l), &addr.full, &val, f) +#define ReadWait(addr, val) PartialMachineCycle(PartialMachineCycle::ReadWait, HalfCycles(2), &addr.full, &val, true) #define ReadEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Read, HalfCycles(3), &addr.full, &val, false) #define WriteStart(addr, val) PartialMachineCycle(PartialMachineCycle::WriteStart,HalfCycles(3), &addr.full, &val, false) -#define WriteWait(l, addr, val, f) PartialMachineCycle(PartialMachineCycle::WriteWait, HalfCycles(l), &addr.full, &val, f) +#define WriteWait(addr, val) PartialMachineCycle(PartialMachineCycle::WriteWait, HalfCycles(2), &addr.full, &val, true) #define WriteEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Write, HalfCycles(3), &addr.full, &val, false) #define InputStart(addr, val) PartialMachineCycle(PartialMachineCycle::InputStart, HalfCycles(3), &addr.full, &val, false) @@ -47,32 +47,15 @@ ProcessorStorage::ProcessorStorage() { #define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op} // Compound bus operations, as micro-ops - -// Read3 is a standard read cycle: 1.5 cycles, then check the wait line, then 1.5 cycles; -// Read4 is a four-cycle read that has to do something to calculate the address: 1.5 cycles, then an extra wait cycle, then check the wait line, then 1.5 cycles; -// Read4Pre is a four-cycle read that has to do something after reading: 1.5 cycles, then check the wait line, then an extra wait cycle, then 1.5 cycles; -// Read5 is a five-cycle read: 1.5 cycles, two wait cycles, check the wait line, 1.5 cycles. -#define Read3(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)) -#define Read4(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, false)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)) -#define Read4Pre(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadWait(2, addr, val, false)), BusOp(ReadEnd(addr, val)) -#define Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(4, addr, val, false)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)) - -#define Write3(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val)) -#define Write5(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(4, addr, val, false)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val)) - -#define Input(addr, val) BusOp(InputStart(addr, val)), BusOp(InputWait(addr, val, false)), BusOp(InputWait(addr, val, true)), BusOp(InputEnd(addr, val)) -#define Output(addr, val) BusOp(OutputStart(addr, val)), BusOp(OutputWait(addr, val, false)), BusOp(OutputWait(addr, val, true)), BusOp(OutputEnd(addr, val)) -#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), nullptr, nullptr, false}} +#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}} +#define Read(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(addr, val)), BusOp(ReadEnd(addr, val)) +#define Write(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(addr, val)), BusOp(WriteEnd(addr, val)) +#define Input(addr, val) BusOp(InputStart(addr, val)), BusOp(InputWait(addr, val, false)), BusOp(InputWait(addr, val, true)), BusOp(InputEnd(addr, val)) +#define Output(addr, val) BusOp(OutputStart(addr, val)), BusOp(OutputWait(addr, val, false)), BusOp(OutputWait(addr, val, true)), BusOp(OutputEnd(addr, val)) /// A sequence is a series of micro-ops that ends in a move-to-next-program operation. #define Sequence(...) { __VA_ARGS__, {MicroOp::MoveToNextProgram} } -/// An instruction is the part of an instruction that follows instruction fetch; it should include two or more refresh cycles and then the work of the instruction. -#define Instr(r, ...) Sequence(BusOp(Refresh(r)), __VA_ARGS__) - -/// A standard instruction is one with the most normal timing: two cycles of refresh, then the work. -#define StdInstr(...) Instr(4, __VA_ARGS__) - // Assumption made: those instructions that are rated with an opcode fetch greater than four cycles spend the extra time // providing a lengthened refresh cycle. I assume this because the CPU doesn't have foresight and presumably spends the // normal refresh time decoding. So if it gets to cycle four and realises it has two more cycles of work, I have assumed @@ -82,65 +65,60 @@ ProcessorStorage::ProcessorStorage() { #define Inc16(r) {(&r == &pc_) ? MicroOp::IncrementPC : MicroOp::Increment16, &r.full} #define Inc8NoFlags(r) {MicroOp::Increment8NoFlags, &r} -#define ReadInc(addr, val) Read3(addr, val), Inc16(addr) -#define Read4Inc(addr, val) Read4(addr, val), Inc16(addr) -#define Read5Inc(addr, val) Read5(addr, val), Inc16(addr) -#define WriteInc(addr, val) Write3(addr, val), {MicroOp::Increment16, &addr.full} +#define ReadInc(addr, val) Read(addr, val), Inc16(addr) +#define WriteInc(addr, val) Write(addr, val), {MicroOp::Increment16, &addr.full} #define Read16Inc(addr, val) ReadInc(addr, val.halves.low), ReadInc(addr, val.halves.high) -#define Read16(addr, val) ReadInc(addr, val.halves.low), Read3(addr, val.halves.high) +#define Read16(addr, val) ReadInc(addr, val.halves.low), Read(addr, val.halves.high) -#define Write16(addr, val) WriteInc(addr, val.halves.low), Write3(addr, val.halves.high) +#define Write16(addr, val) WriteInc(addr, val.halves.low), Write(addr, val.halves.high) #define INDEX() {MicroOp::IndexedPlaceHolder}, ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &index} #define FINDEX() {MicroOp::IndexedPlaceHolder}, ReadInc(pc_, temp8_), {MicroOp::CalculateIndexAddress, &index} #define INDEX_ADDR() (add_offsets ? memptr_ : index) -#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.low) -#define Pop(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read3(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full} - -#define Push8(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write5(sp_, x.halves.low) -#define Pop7(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read4(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full} +#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write(sp_, x.halves.low) +#define Pop(x) Read(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full} /* The following are actual instructions */ -#define NOP Sequence(BusOp(Refresh(4))) +#define NOP { {MicroOp::MoveToNextProgram} } -#define JP(cc) StdInstr(Read16Inc(pc_, memptr_), {MicroOp::cc, nullptr}, {MicroOp::Move16, &memptr_.full, &pc_.full}) -#define CALL(cc) StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}) -#define RET(cc) Instr(6, {MicroOp::cc, nullptr}, Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}) -#define JR(cc) StdInstr(ReadInc(pc_, temp8_), {MicroOp::cc, nullptr}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}) -#define RST() Instr(6, {MicroOp::CalculateRSTDestination}, Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}) -#define LD(a, b) StdInstr({MicroOp::Move8, &b, &a}) +#define JP(cc) Sequence(Read16Inc(pc_, memptr_), {MicroOp::cc, nullptr}, {MicroOp::Move16, &memptr_.full, &pc_.full}) +#define CALL(cc) Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, ReadInc(pc_, memptr_.halves.high), InternalOperation(2), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}) +#define RET(cc) Sequence(InternalOperation(2), {MicroOp::cc, nullptr}, Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}) +#define JR(cc) Sequence(ReadInc(pc_, temp8_), {MicroOp::cc, nullptr}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}) +#define RST() Sequence(InternalOperation(2), {MicroOp::CalculateRSTDestination}, Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}) +#define LD(a, b) Sequence({MicroOp::Move8, &b, &a}) #define LD_GROUP(r, ri) \ LD(r, bc_.halves.high), LD(r, bc_.halves.low), LD(r, de_.halves.high), LD(r, de_.halves.low), \ LD(r, index.halves.high), LD(r, index.halves.low), \ - StdInstr(INDEX(), Read3(INDEX_ADDR(), temp8_), {MicroOp::Move8, &temp8_, &ri}), \ + Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::Move8, &temp8_, &ri}), \ LD(r, a_) #define READ_OP_GROUP(op) \ - StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \ - StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \ - StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \ - StdInstr(INDEX(), Read3(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr({MicroOp::op, &a_}) + Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \ + Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \ + Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \ + Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence({MicroOp::op, &a_}) #define READ_OP_GROUP_D(op) \ - StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \ - StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \ - StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \ - StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr({MicroOp::op, &a_}) + Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \ + Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \ + Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \ + Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence({MicroOp::op, &a_}) -#define RMW(x, op, ...) StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x)) -#define RMWI(x, op, ...) StdInstr(Read4(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x)) +#define RMW(x, op, ...) Sequence(INDEX(), Read(INDEX_ADDR(), x), InternalOperation(2), {MicroOp::op, &x}, Write(INDEX_ADDR(), x)) +#define RMWI(x, op, ...) Sequence(Read(INDEX_ADDR(), x), InternalOperation(2), {MicroOp::op, &x}, Write(INDEX_ADDR(), x)) #define MODIFY_OP_GROUP(op) \ - StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \ - StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \ - StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \ + Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \ + Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \ + Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \ RMW(temp8_, op), \ - StdInstr({MicroOp::op, &a_}) + Sequence({MicroOp::op, &a_}) #define IX_MODIFY_OP_GROUP(op) \ RMWI(bc_.halves.high, op), \ @@ -153,18 +131,18 @@ ProcessorStorage::ProcessorStorage() { RMWI(a_, op) #define IX_READ_OP_GROUP(op) \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}) + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ + Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}) -#define ADD16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::ADD16, &s.full, &d.full}) -#define ADC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::ADC16, &s.full, &d.full}) -#define SBC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full}) +#define ADD16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::ADD16, &s.full, &d.full}) +#define ADC16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::ADC16, &s.full, &d.full}) +#define SBC16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full}) void ProcessorStorage::install_default_instruction_set() { MicroOp conditional_call_untaken_program[] = Sequence(ReadInc(pc_, memptr_.halves.high)); @@ -175,11 +153,8 @@ void ProcessorStorage::install_default_instruction_set() { assemble_base_page(fd_page_, iy_, true, fdcb_page_); assemble_ed_page(ed_page_); - fdcb_page_.r_step = 0; fd_page_.is_indexed = true; fdcb_page_.is_indexed = true; - - ddcb_page_.r_step = 0; dd_page_.is_indexed = true; ddcb_page_.is_indexed = true; @@ -202,7 +177,8 @@ void ProcessorStorage::install_default_instruction_set() { BusOp(ReadOpcodeStart()), BusOp(ReadOpcodeWait(true)), BusOp(ReadOpcodeEnd()), - BusOp(Refresh(6)), + BusOp(Refresh()), + InternalOperation(2), Push(pc_), { MicroOp::JumpTo66, nullptr, nullptr}, { MicroOp::MoveToNextProgram } @@ -212,14 +188,14 @@ void ProcessorStorage::install_default_instruction_set() { BusOp(IntAckStart(5, operation_)), BusOp(IntWait(operation_)), BusOp(IntAckEnd(operation_)), - { MicroOp::DecodeOperationNoRChange } + { MicroOp::DecodeOperation } }; MicroOp irq_mode1_program[] = { { MicroOp::BeginIRQ }, BusOp(IntAckStart(7, operation_)), // 7 half cycles (including + BusOp(IntWait(operation_)), // [potentially 2 half cycles] + BusOp(IntAckEnd(operation_)), // Implicitly 3 half cycles + - BusOp(Refresh(4)), // 4 half cycles + + BusOp(Refresh()), // 4 half cycles + Push(pc_), // 12 half cycles = 26 half cycles = 13 cycles { MicroOp::Move16, &temp16_.full, &pc_.full }, { MicroOp::MoveToNextProgram } @@ -229,7 +205,7 @@ void ProcessorStorage::install_default_instruction_set() { BusOp(IntAckStart(7, temp16_.halves.low)), BusOp(IntWait(temp16_.halves.low)), BusOp(IntAckEnd(temp16_.halves.low)), - BusOp(Refresh(4)), + BusOp(Refresh()), Push(pc_), { MicroOp::Move8, &ir_.halves.high, &temp16_.halves.high }, Read16(temp16_, pc_), @@ -244,8 +220,8 @@ void ProcessorStorage::install_default_instruction_set() { } void ProcessorStorage::assemble_ed_page(InstructionPage &target) { -#define IN_C(r) StdInstr({MicroOp::Move16, &bc_.full, &memptr_.full}, Input(bc_, r), {MicroOp::SetInFlags, &r}) -#define OUT_C(r) StdInstr(Output(bc_, r), {MicroOp::SetOutFlags}) +#define IN_C(r) Sequence({MicroOp::Move16, &bc_.full, &memptr_.full}, Input(bc_, r), {MicroOp::SetInFlags, &r}) +#define OUT_C(r) Sequence(Output(bc_, r), {MicroOp::SetOutFlags}) #define IN_OUT(r) IN_C(r), OUT_C(r) #define NOP_ROW() NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP @@ -255,58 +231,58 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) { NOP_ROW(), /* 0x20 */ NOP_ROW(), /* 0x30 */ /* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(bc_.halves.high), - /* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, bc_)), - /* 0x44 NEG */ StdInstr({MicroOp::NEG}), /* 0x45 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x46 IM 0 */ StdInstr({MicroOp::IM}), /* 0x47 LD I, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.halves.high}), + /* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, bc_)), + /* 0x44 NEG */ Sequence({MicroOp::NEG}), /* 0x45 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x46 IM 0 */ Sequence({MicroOp::IM}), /* 0x47 LD I, A */ Sequence(InternalOperation(2), {MicroOp::Move8, &a_, &ir_.halves.high}), /* 0x48 IN C, (C); 0x49 OUT (C), C */ IN_OUT(bc_.halves.low), - /* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, bc_)), - /* 0x4c NEG */ StdInstr({MicroOp::NEG}), /* 0x4d RETI */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x4e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x4f LD R, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.halves.low}), + /* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, bc_)), + /* 0x4c NEG */ Sequence({MicroOp::NEG}), /* 0x4d RETI */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x4e IM 0/1 */ Sequence({MicroOp::IM}), /* 0x4f LD R, A */ Sequence(InternalOperation(2), {MicroOp::Move8, &a_, &ir_.halves.low}), /* 0x50 IN D, (C); 0x51 OUT (C), D */ IN_OUT(de_.halves.high), - /* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, de_)), - /* 0x54 NEG */ StdInstr({MicroOp::NEG}), /* 0x55 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x56 IM 1 */ StdInstr({MicroOp::IM}), /* 0x57 LD A, I */ Instr(6, {MicroOp::Move8, &ir_.halves.high, &a_}, {MicroOp::SetAFlags}), + /* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, de_)), + /* 0x54 NEG */ Sequence({MicroOp::NEG}), /* 0x55 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x56 IM 1 */ Sequence({MicroOp::IM}), /* 0x57 LD A, I */ Sequence(InternalOperation(2), {MicroOp::Move8, &ir_.halves.high, &a_}, {MicroOp::SetAFlags}), /* 0x58 IN E, (C); 0x59 OUT (C), E */ IN_OUT(de_.halves.low), - /* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, de_)), - /* 0x5c NEG */ StdInstr({MicroOp::NEG}), /* 0x5d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x5e IM 2 */ StdInstr({MicroOp::IM}), /* 0x5f LD A, R */ Instr(6, {MicroOp::Move8, &ir_.halves.low, &a_}, {MicroOp::SetAFlags}), + /* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, de_)), + /* 0x5c NEG */ Sequence({MicroOp::NEG}), /* 0x5d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x5e IM 2 */ Sequence({MicroOp::IM}), /* 0x5f LD A, R */ Sequence(InternalOperation(2), {MicroOp::Move8, &ir_.halves.low, &a_}, {MicroOp::SetAFlags}), /* 0x60 IN H, (C); 0x61 OUT (C), H */ IN_OUT(hl_.halves.high), - /* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, hl_)), - /* 0x64 NEG */ StdInstr({MicroOp::NEG}), /* 0x65 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x66 IM 0 */ StdInstr({MicroOp::IM}), /* 0x67 RRD */ StdInstr(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write3(hl_, temp8_)), + /* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, hl_)), + /* 0x64 NEG */ Sequence({MicroOp::NEG}), /* 0x65 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x66 IM 0 */ Sequence({MicroOp::IM}), /* 0x67 RRD */ Sequence(Read(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write(hl_, temp8_)), /* 0x68 IN L, (C); 0x69 OUT (C), L */ IN_OUT(hl_.halves.low), - /* 0x6a ADC HL, HL */ ADC16(hl_, hl_), /* 0x6b LD HL, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, hl_)), - /* 0x6c NEG */ StdInstr({MicroOp::NEG}), /* 0x6d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x6e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x6f RLD */ StdInstr(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write3(hl_, temp8_)), - /* 0x70 IN (C) */ IN_C(temp8_), /* 0x71 OUT (C), 0 */ StdInstr({MicroOp::SetZero}, Output(bc_, temp8_), {MicroOp::SetOutFlags}), - /* 0x72 SBC HL, SP */ SBC16(hl_, sp_), /* 0x73 LD (nn), SP */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, sp_)), - /* 0x74 NEG */ StdInstr({MicroOp::NEG}), /* 0x75 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x76 IM 1 */ StdInstr({MicroOp::IM}), /* 0x77 XX */ NOP, + /* 0x6a ADC HL, HL */ ADC16(hl_, hl_), /* 0x6b LD HL, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, hl_)), + /* 0x6c NEG */ Sequence({MicroOp::NEG}), /* 0x6d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x6e IM 0/1 */ Sequence({MicroOp::IM}), /* 0x6f RLD */ Sequence(Read(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write(hl_, temp8_)), + /* 0x70 IN (C) */ IN_C(temp8_), /* 0x71 OUT (C), 0 */ Sequence({MicroOp::SetZero}, Output(bc_, temp8_), {MicroOp::SetOutFlags}), + /* 0x72 SBC HL, SP */ SBC16(hl_, sp_), /* 0x73 LD (nn), SP */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, sp_)), + /* 0x74 NEG */ Sequence({MicroOp::NEG}), /* 0x75 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x76 IM 1 */ Sequence({MicroOp::IM}), /* 0x77 XX */ NOP, /* 0x78 IN A, (C); 0x79 OUT (C), A */ IN_OUT(a_), - /* 0x7a ADC HL, SP */ ADC16(hl_, sp_), /* 0x7b LD SP, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, sp_)), - /* 0x7c NEG */ StdInstr({MicroOp::NEG}), /* 0x7d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x7e IM 2 */ StdInstr({MicroOp::IM}), /* 0x7f XX */ NOP, + /* 0x7a ADC HL, SP */ ADC16(hl_, sp_), /* 0x7b LD SP, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, sp_)), + /* 0x7c NEG */ Sequence({MicroOp::NEG}), /* 0x7d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}), + /* 0x7e IM 2 */ Sequence({MicroOp::IM}), /* 0x7f XX */ NOP, NOP_ROW(), /* 0x80 ... 0x8f */ NOP_ROW(), /* 0x90 ... 0x9f */ - /* 0xa0 LDI */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDI}), - /* 0xa1 CPI */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}), - /* 0xa2 INI */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INI}), - /* 0xa3 OTI */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_)), + /* 0xa0 LDI */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDI}), + /* 0xa1 CPI */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}), + /* 0xa2 INI */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INI}), + /* 0xa3 OTI */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_)), NOP, NOP, NOP, NOP, - /* 0xa8 LDD */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDD}), - /* 0xa9 CPD */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}), - /* 0xaa IND */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::IND}), - /* 0xab OTD */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_)), + /* 0xa8 LDD */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDD}), + /* 0xa9 CPD */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}), + /* 0xaa IND */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::IND}), + /* 0xab OTD */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_)), NOP, NOP, NOP, NOP, - /* 0xb0 LDIR */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDIR}, InternalOperation(10)), - /* 0xb1 CPIR */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)), - /* 0xb2 INIR */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)), - /* 0xb3 OTIR */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)), + /* 0xb0 LDIR */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDIR}, InternalOperation(10)), + /* 0xb1 CPIR */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)), + /* 0xb2 INIR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)), + /* 0xb3 OTIR */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)), NOP, NOP, NOP, NOP, - /* 0xb8 LDDR */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDDR}, InternalOperation(10)), - /* 0xb9 CPDR */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)), - /* 0xba INDR */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)), - /* 0xbb OTDR */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)), + /* 0xb8 LDDR */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDDR}, InternalOperation(10)), + /* 0xb9 CPDR */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)), + /* 0xba INDR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)), + /* 0xbb OTDR */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)), NOP, NOP, NOP, NOP, NOP_ROW(), /* 0xc0 */ NOP_ROW(), /* 0xd0 */ @@ -346,77 +322,77 @@ void ProcessorStorage::assemble_cb_page(InstructionPage &target, RegisterPair16 void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets, InstructionPage &cb_page) { #define INC_DEC_LD(r) \ - StdInstr({MicroOp::Increment8, &r}), \ - StdInstr({MicroOp::Decrement8, &r}), \ - StdInstr(ReadInc(pc_, r)) + Sequence({MicroOp::Increment8, &r}), \ + Sequence({MicroOp::Decrement8, &r}), \ + Sequence(ReadInc(pc_, r)) #define INC_INC_DEC_LD(rf, r) \ - Instr(8, {MicroOp::Increment16, &rf.full}), INC_DEC_LD(r) + Sequence(InternalOperation(4), {MicroOp::Increment16, &rf.full}), INC_DEC_LD(r) #define DEC_INC_DEC_LD(rf, r) \ - Instr(8, {MicroOp::Decrement16, &rf.full}), INC_DEC_LD(r) + Sequence(InternalOperation(4), {MicroOp::Decrement16, &rf.full}), INC_DEC_LD(r) InstructionTable base_program_table = { - /* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ StdInstr(Read16Inc(pc_, bc_)), - /* 0x02 LD (BC), A */ StdInstr({MicroOp::SetAddrAMemptr, &bc_.full}, Write3(bc_, a_)), + /* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ Sequence(Read16Inc(pc_, bc_)), + /* 0x02 LD (BC), A */ Sequence({MicroOp::SetAddrAMemptr, &bc_.full}, Write(bc_, a_)), /* 0x03 INC BC; 0x04 INC B; 0x05 DEC B; 0x06 LD B, n */ INC_INC_DEC_LD(bc_, bc_.halves.high), - /* 0x07 RLCA */ StdInstr({MicroOp::RLCA}), - /* 0x08 EX AF, AF' */ StdInstr({MicroOp::ExAFAFDash}), /* 0x09 ADD HL, BC */ ADD16(index, bc_), - /* 0x0a LD A, (BC) */ StdInstr({MicroOp::Move16, &bc_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)), + /* 0x07 RLCA */ Sequence({MicroOp::RLCA}), + /* 0x08 EX AF, AF' */ Sequence({MicroOp::ExAFAFDash}), /* 0x09 ADD HL, BC */ ADD16(index, bc_), + /* 0x0a LD A, (BC) */ Sequence({MicroOp::Move16, &bc_.full, &memptr_.full}, Read(memptr_, a_), Inc16(memptr_)), /* 0x0b DEC BC; 0x0c INC C; 0x0d DEC C; 0x0e LD C, n */ DEC_INC_DEC_LD(bc_, bc_.halves.low), - /* 0x0f RRCA */ StdInstr({MicroOp::RRCA}), - /* 0x10 DJNZ */ Instr(6, ReadInc(pc_, temp8_), {MicroOp::DJNZ}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}), - /* 0x11 LD DE, nn */ StdInstr(Read16Inc(pc_, de_)), - /* 0x12 LD (DE), A */ StdInstr({MicroOp::SetAddrAMemptr, &de_.full}, Write3(de_, a_)), + /* 0x0f RRCA */ Sequence({MicroOp::RRCA}), + /* 0x10 DJNZ */ Sequence(InternalOperation(2), ReadInc(pc_, temp8_), {MicroOp::DJNZ}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}), + /* 0x11 LD DE, nn */ Sequence(Read16Inc(pc_, de_)), + /* 0x12 LD (DE), A */ Sequence({MicroOp::SetAddrAMemptr, &de_.full}, Write(de_, a_)), /* 0x13 INC DE; 0x14 INC D; 0x15 DEC D; 0x16 LD D, n */ INC_INC_DEC_LD(de_, de_.halves.high), - /* 0x17 RLA */ StdInstr({MicroOp::RLA}), - /* 0x18 JR */ StdInstr(ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}), + /* 0x17 RLA */ Sequence({MicroOp::RLA}), + /* 0x18 JR */ Sequence(ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}), /* 0x19 ADD HL, DE */ ADD16(index, de_), - /* 0x1a LD A, (DE) */ StdInstr({MicroOp::Move16, &de_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)), + /* 0x1a LD A, (DE) */ Sequence({MicroOp::Move16, &de_.full, &memptr_.full}, Read(memptr_, a_), Inc16(memptr_)), /* 0x1b DEC DE; 0x1c INC E; 0x1d DEC E; 0x1e LD E, n */ DEC_INC_DEC_LD(de_, de_.halves.low), - /* 0x1f RRA */ StdInstr({MicroOp::RRA}), - /* 0x20 JR NZ */ JR(TestNZ), /* 0x21 LD HL, nn */ StdInstr(Read16Inc(pc_, index)), - /* 0x22 LD (nn), HL */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, index)), + /* 0x1f RRA */ Sequence({MicroOp::RRA}), + /* 0x20 JR NZ */ JR(TestNZ), /* 0x21 LD HL, nn */ Sequence(Read16Inc(pc_, index)), + /* 0x22 LD (nn), HL */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, index)), /* 0x23 INC HL; 0x24 INC H; 0x25 DEC H; 0x26 LD H, n */ INC_INC_DEC_LD(index, index.halves.high), - /* 0x27 DAA */ StdInstr({MicroOp::DAA}), + /* 0x27 DAA */ Sequence({MicroOp::DAA}), /* 0x28 JR Z */ JR(TestZ), /* 0x29 ADD HL, HL */ ADD16(index, index), - /* 0x2a LD HL, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, index)), + /* 0x2a LD HL, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, index)), /* 0x2b DEC HL; 0x2c INC L; 0x2d DEC L; 0x2e LD L, n */ DEC_INC_DEC_LD(index, index.halves.low), - /* 0x2f CPL */ StdInstr({MicroOp::CPL}), - /* 0x30 JR NC */ JR(TestNC), /* 0x31 LD SP, nn */ StdInstr(Read16Inc(pc_, sp_)), - /* 0x32 LD (nn), A */ StdInstr(Read16Inc(pc_, temp16_), {MicroOp::SetAddrAMemptr, &temp16_.full}, Write3(temp16_, a_)), - /* 0x33 INC SP */ Instr(8, {MicroOp::Increment16, &sp_.full}), - /* 0x34 INC (HL) */ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Increment8, &temp8_}, Write3(INDEX_ADDR(), temp8_)), - /* 0x35 DEC (HL) */ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Decrement8, &temp8_}, Write3(INDEX_ADDR(), temp8_)), - /* 0x36 LD (HL), n */ StdInstr(ReadInc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_)), - /* 0x37 SCF */ StdInstr({MicroOp::SCF}), + /* 0x2f CPL */ Sequence({MicroOp::CPL}), + /* 0x30 JR NC */ JR(TestNC), /* 0x31 LD SP, nn */ Sequence(Read16Inc(pc_, sp_)), + /* 0x32 LD (nn), A */ Sequence(Read16Inc(pc_, temp16_), {MicroOp::SetAddrAMemptr, &temp16_.full}, Write(temp16_, a_)), + /* 0x33 INC SP */ Sequence(InternalOperation(4), {MicroOp::Increment16, &sp_.full}), + /* 0x34 INC (HL) */ Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::Increment8, &temp8_}, Write(INDEX_ADDR(), temp8_)), + /* 0x35 DEC (HL) */ Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::Decrement8, &temp8_}, Write(INDEX_ADDR(), temp8_)), + /* 0x36 LD (HL), n */ Sequence(ReadInc(pc_, temp8_), Write(INDEX_ADDR(), temp8_)), + /* 0x37 SCF */ Sequence({MicroOp::SCF}), /* 0x38 JR C */ JR(TestC), /* 0x39 ADD HL, SP */ ADD16(index, sp_), - /* 0x3a LD A, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read3(memptr_, a_), Inc16(memptr_)), - /* 0x3b DEC SP */ Instr(8, {MicroOp::Decrement16, &sp_.full}), + /* 0x3a LD A, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read(memptr_, a_), Inc16(memptr_)), + /* 0x3b DEC SP */ Sequence(InternalOperation(4), {MicroOp::Decrement16, &sp_.full}), /* 0x3c INC A; 0x3d DEC A; 0x3e LD A, n */ INC_DEC_LD(a_), - /* 0x3f CCF */ StdInstr({MicroOp::CCF}), + /* 0x3f CCF */ Sequence({MicroOp::CCF}), /* 0x40 LD B, B; 0x41 LD B, C; 0x42 LD B, D; 0x43 LD B, E; 0x44 LD B, H; 0x45 LD B, L; 0x46 LD B, (HL); 0x47 LD B, A */ LD_GROUP(bc_.halves.high, bc_.halves.high), @@ -436,14 +412,14 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 0x68 LD L, B; 0x69 LD L, C; 0x6a LD L, D; 0x6b LD L, E; 0x6c LD L, H; 0x6d LD H, L; 0x6e LD L, (HL); 0x6f LD L, A */ LD_GROUP(index.halves.low, hl_.halves.low), - /* 0x70 LD (HL), B */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.halves.high)), - /* 0x71 LD (HL), C */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.halves.low)), - /* 0x72 LD (HL), D */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.halves.high)), - /* 0x73 LD (HL), E */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.halves.low)), - /* 0x74 LD (HL), H */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register; - /* 0x75 LD (HL), L */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L. - /* 0x76 HALT */ StdInstr({MicroOp::HALT}), - /* 0x77 LD (HL), A */ StdInstr(INDEX(), Write3(INDEX_ADDR(), a_)), + /* 0x70 LD (HL), B */ Sequence(INDEX(), Write(INDEX_ADDR(), bc_.halves.high)), + /* 0x71 LD (HL), C */ Sequence(INDEX(), Write(INDEX_ADDR(), bc_.halves.low)), + /* 0x72 LD (HL), D */ Sequence(INDEX(), Write(INDEX_ADDR(), de_.halves.high)), + /* 0x73 LD (HL), E */ Sequence(INDEX(), Write(INDEX_ADDR(), de_.halves.low)), + /* 0x74 LD (HL), H */ Sequence(INDEX(), Write(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register; + /* 0x75 LD (HL), L */ Sequence(INDEX(), Write(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L. + /* 0x76 HALT */ Sequence({MicroOp::HALT}), + /* 0x77 LD (HL), A */ Sequence(INDEX(), Write(INDEX_ADDR(), a_)), /* 0x78 LD A, B; 0x79 LD A, C; 0x7a LD A, D; 0x7b LD A, E; 0x7c LD A, H; 0x7d LD A, L; 0x7e LD A, (HL); 0x7f LD A, A */ LD_GROUP(a_, a_), @@ -472,45 +448,45 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 0xb8 CP B; 0xb9 CP C; 0xba CP D; 0xbb CP E; 0xbc CP H; 0xbd CP L; 0xbe CP (HL); 0xbf CP A */ READ_OP_GROUP(CP8), - /* 0xc0 RET NZ */ RET(TestNZ), /* 0xc1 POP BC */ StdInstr(Pop(bc_)), - /* 0xc2 JP NZ */ JP(TestNZ), /* 0xc3 JP nn */ StdInstr(Read16(pc_, memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}), - /* 0xc4 CALL NZ */ CALL(TestNZ), /* 0xc5 PUSH BC */ Instr(6, Push(bc_)), - /* 0xc6 ADD A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADD8, &temp8_}), + /* 0xc0 RET NZ */ RET(TestNZ), /* 0xc1 POP BC */ Sequence(Pop(bc_)), + /* 0xc2 JP NZ */ JP(TestNZ), /* 0xc3 JP nn */ Sequence(Read16(pc_, memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}), + /* 0xc4 CALL NZ */ CALL(TestNZ), /* 0xc5 PUSH BC */ Sequence(InternalOperation(2), Push(bc_)), + /* 0xc6 ADD A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::ADD8, &temp8_}), /* 0xc7 RST 00h */ RST(), - /* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ StdInstr(Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}), - /* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */StdInstr(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}), - /* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ StdInstr(ReadInc(pc_, memptr_.halves.low), Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}), - /* 0xce ADC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADC8, &temp8_}), + /* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ Sequence(Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}), + /* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */Sequence(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}), + /* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ Sequence(ReadInc(pc_, memptr_.halves.low), ReadInc(pc_, memptr_.halves.high), InternalOperation(2), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}), + /* 0xce ADC A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::ADC8, &temp8_}), /* 0xcf RST 08h */ RST(), - /* 0xd0 RET NC */ RET(TestNC), /* 0xd1 POP DE */ StdInstr(Pop(de_)), - /* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Output(memptr_, a_), Inc8NoFlags(memptr_.halves.low)), - /* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Instr(6, Push(de_)), - /* 0xd6 SUB n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SUB8, &temp8_}), + /* 0xd0 RET NC */ RET(TestNC), /* 0xd1 POP DE */ Sequence(Pop(de_)), + /* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Output(memptr_, a_), Inc8NoFlags(memptr_.halves.low)), + /* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Sequence(InternalOperation(2), Push(de_)), + /* 0xd6 SUB n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::SUB8, &temp8_}), /* 0xd7 RST 10h */ RST(), - /* 0xd8 RET C */ RET(TestC), /* 0xd9 EXX */ StdInstr({MicroOp::EXX}), - /* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Input(memptr_, a_), Inc16(memptr_)), - /* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */StdInstr({MicroOp::SetInstructionPage, &dd_page_}), - /* 0xde SBC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}), + /* 0xd8 RET C */ RET(TestC), /* 0xd9 EXX */ Sequence({MicroOp::EXX}), + /* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Input(memptr_, a_), Inc16(memptr_)), + /* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */Sequence({MicroOp::SetInstructionPage, &dd_page_}), + /* 0xde SBC A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}), /* 0xdf RST 18h */ RST(), - /* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ StdInstr(Pop(index)), - /* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */StdInstr(Pop7(memptr_), Push8(index), {MicroOp::Move16, &memptr_.full, &index.full}), - /* 0xe4 CALL PO */ CALL(TestPO), /* 0xe5 PUSH HL */ Instr(6, Push(index)), - /* 0xe6 AND n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::And, &temp8_}), + /* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ Sequence(Pop(index)), + /* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */Sequence(Pop(memptr_), InternalOperation(2), Push(index), InternalOperation(4), {MicroOp::Move16, &memptr_.full, &index.full}), + /* 0xe4 CALL PO */ CALL(TestPO), /* 0xe5 PUSH HL */ Sequence(InternalOperation(2), Push(index)), + /* 0xe6 AND n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::And, &temp8_}), /* 0xe7 RST 20h */ RST(), - /* 0xe8 RET PE */ RET(TestPE), /* 0xe9 JP (HL) */ StdInstr({MicroOp::Move16, &index.full, &pc_.full}), - /* 0xea JP PE */ JP(TestPE), /* 0xeb EX DE, HL */StdInstr({MicroOp::ExDEHL}), - /* 0xec CALL PE */ CALL(TestPE), /* 0xed [ED page] */StdInstr({MicroOp::SetInstructionPage, &ed_page_}), - /* 0xee XOR n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::Xor, &temp8_}), + /* 0xe8 RET PE */ RET(TestPE), /* 0xe9 JP (HL) */ Sequence({MicroOp::Move16, &index.full, &pc_.full}), + /* 0xea JP PE */ JP(TestPE), /* 0xeb EX DE, HL */Sequence({MicroOp::ExDEHL}), + /* 0xec CALL PE */ CALL(TestPE), /* 0xed [ED page] */Sequence({MicroOp::SetInstructionPage, &ed_page_}), + /* 0xee XOR n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::Xor, &temp8_}), /* 0xef RST 28h */ RST(), - /* 0xf0 RET p */ RET(TestP), /* 0xf1 POP AF */ StdInstr(Pop(temp16_), {MicroOp::DisassembleAF}), - /* 0xf2 JP P */ JP(TestP), /* 0xf3 DI */ StdInstr({MicroOp::DI}), - /* 0xf4 CALL P */ CALL(TestP), /* 0xf5 PUSH AF */ Instr(6, {MicroOp::AssembleAF}, Push(temp16_)), - /* 0xf6 OR n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::Or, &temp8_}), + /* 0xf0 RET p */ RET(TestP), /* 0xf1 POP AF */ Sequence(Pop(temp16_), {MicroOp::DisassembleAF}), + /* 0xf2 JP P */ JP(TestP), /* 0xf3 DI */ Sequence({MicroOp::DI}), + /* 0xf4 CALL P */ CALL(TestP), /* 0xf5 PUSH AF */ Sequence(InternalOperation(2), {MicroOp::AssembleAF}, Push(temp16_)), + /* 0xf6 OR n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::Or, &temp8_}), /* 0xf7 RST 30h */ RST(), - /* 0xf8 RET M */ RET(TestM), /* 0xf9 LD SP, HL */Instr(8, {MicroOp::Move16, &index.full, &sp_.full}), - /* 0xfa JP M */ JP(TestM), /* 0xfb EI */ StdInstr({MicroOp::EI}), - /* 0xfc CALL M */ CALL(TestM), /* 0xfd [FD page] */StdInstr({MicroOp::SetInstructionPage, &fd_page_}), - /* 0xfe CP n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::CP8, &temp8_}), + /* 0xf8 RET M */ RET(TestM), /* 0xf9 LD SP, HL */Sequence(InternalOperation(4), {MicroOp::Move16, &index.full, &sp_.full}), + /* 0xfa JP M */ JP(TestM), /* 0xfb EI */ Sequence({MicroOp::EI}), + /* 0xfc CALL M */ CALL(TestM), /* 0xfd [FD page] */Sequence({MicroOp::SetInstructionPage, &fd_page_}), + /* 0xfe CP n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::CP8, &temp8_}), /* 0xff RST 38h */ RST(), }; @@ -518,7 +494,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 // The indexed version of 0x36 differs substantially from the non-indexed by building index calculation into // the cycle that fetches the final operand. So patch in a different microprogram if building an indexed table. InstructionTable copy_table = { - StdInstr(FINDEX(), Read5Inc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_)) + Sequence(FINDEX(), ReadInc(pc_, temp8_), InternalOperation(4), Write(INDEX_ADDR(), temp8_)) }; std::memcpy(&base_program_table[0x36], ©_table[0], sizeof(copy_table[0])); } @@ -528,19 +504,27 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 } void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, int length) { + /// The fetch-decode-execute sequence for a regular four-clock M1 cycle. const MicroOp normal_fetch_decode_execute[] = { BusOp(ReadOpcodeStart()), BusOp(ReadOpcodeWait(true)), BusOp(ReadOpcodeEnd()), + { MicroOp::IncrementR }, + BusOp(Refresh()), { MicroOp::DecodeOperation } }; + + /// The concluding fetch-decode-execute of a [dd/fd]cb nn oo sequence, i.e. an (IX+n) or (IY+n) operation. + /// Per the observed 48kb/128kb Spectrum timings, this appears not to include a refresh cycle. So I've also + /// taken a punt on it not incrementing R. const MicroOp short_fetch_decode_execute[] = { - BusOp(ReadOpcodeStart()), - BusOp(ReadOpcodeWait(false)), - BusOp(ReadOpcodeWait(true)), - BusOp(ReadOpcodeEnd()), - { MicroOp::DecodeOperation } + BusOp(ReadStart(pc_, operation_)), + BusOp(ReadWait(pc_, operation_)), + BusOp(ReadEnd(pc_, operation_)), + InternalOperation(4), + { MicroOp::DecodeOperation }, }; + copy_program((length == 4) ? normal_fetch_decode_execute : short_fetch_decode_execute, target.fetch_decode_execute); target.fetch_decode_execute_data = target.fetch_decode_execute.data(); } diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index fb0f6bcdc..613e9334a 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -16,8 +16,8 @@ class ProcessorStorage { struct MicroOp { enum Type { BusOperation, + IncrementR, DecodeOperation, - DecodeOperationNoRChange, MoveToNextProgram, Increment8NoFlags, @@ -121,7 +121,6 @@ class ProcessorStorage { std::vector all_operations; std::vector fetch_decode_execute; MicroOp *fetch_decode_execute_data = nullptr; - uint8_t r_step = 1; bool is_indexed = false; }; @@ -149,6 +148,8 @@ class ProcessorStorage { // that knowledge of what the last opcode did is necessary to get bits 5 & 3 // correct for SCF and CCF. + uint16_t last_address_bus_ = 0; // The value most recently put out on the address bus. + HalfCycles number_of_cycles_; enum Interrupt: uint8_t { diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 777fd1cdc..fd8eaad5d 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -68,29 +68,50 @@ enum Flag: uint8_t { */ struct PartialMachineCycle { enum Operation { + /// The final half cycle of the opcode fetch part of an M1 cycle. ReadOpcode = 0, + /// The 1.5 cycles of a read cycle. Read, + /// The 1.5 cycles of a write cycle. Write, + /// The 1.5 cycles of an input cycle. Input, + /// The 1.5 cycles of an output cycle. Output, + /// The 1.5 cycles of an interrupt acknowledgment. Interrupt, + /// The two-cycle refresh part of an M1 cycle. Refresh, + /// A period with no changes in bus signalling. Internal, + /// A bus acknowledgement cycle. BusAcknowledge, + /// A wait state within an M1 cycle. ReadOpcodeWait, + /// A wait state within a read cycle. ReadWait, + /// A wait state within a write cycle. WriteWait, + /// A wait state within an input cycle. InputWait, + /// A wait state within an output cycle. OutputWait, + /// A wait state within an interrupt acknowledge cycle. InterruptWait, + /// The first 1.5 cycles of an M1 bus cycle, up to the sampling of WAIT. ReadOpcodeStart, + /// The first 1.5 cycles of a read cycle, up to the sampling of WAIT. ReadStart, + /// The first 1.5 cycles of a write cycle, up to the sampling of WAIT. WriteStart, + /// The first 1.5 samples of an input bus cycle, up to the sampling of WAIT. InputStart, + /// The first 1.5 samples of an output bus cycle, up to the sampling of WAIT. OutputStart, + /// The first portion of an interrupt acknowledgement — 2.5 or 3.5 cycles, depending on interrupt mode. InterruptStart, }; /// The operation being carried out by the Z80. See the various getters below for better classification. @@ -125,6 +146,230 @@ struct PartialMachineCycle { return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait; } + enum Line { + CLK = 1 << 0, + + MREQ = 1 << 1, + IOREQ = 1 << 2, + + RD = 1 << 3, + WR = 1 << 4, + RFSH = 1 << 5, + + M1 = 1 << 6, + + BUSACK = 1 << 7, + }; + + /// @returns A C-style array of the bus state at the beginning of each half cycle in this + /// partial machine cycle. Each element is a combination of bit masks from the Line enum; + /// bit set means line active, bit clear means line inactive. For the CLK line set means high. + /// + /// @discussion This discrete sampling is prone to aliasing errors. Beware. + const uint8_t *bus_state() const { + switch(operation) { + + // + // M1 cycle + // + + case Operation::ReadOpcodeStart: { + static constexpr uint8_t states[] = { + Line::CLK | Line::M1, + Line::M1 | Line::MREQ | Line::RD, + Line::CLK | Line::M1 | Line::MREQ | Line::RD, + }; + return states; + } + + case Operation::ReadOpcode: + case Operation::ReadOpcodeWait: { + static constexpr uint8_t states[] = { + Line::M1 | Line::MREQ | Line::RD, + Line::CLK | Line::M1 | Line::MREQ | Line::RD, + }; + return states; + } + + case Operation::Refresh: { + static constexpr uint8_t states[] = { + Line::CLK | Line::RFSH | Line::MREQ, + Line::RFSH, + Line::CLK | Line::RFSH | Line::MREQ, + Line::RFSH | Line::MREQ, + Line::CLK | Line::RFSH, + Line::RFSH, + Line::CLK | Line::RFSH, + Line::RFSH, + }; + return states; + } + + // + // Read cycle. + // + + case Operation::ReadStart: { + static constexpr uint8_t states[] = { + Line::CLK, + Line::RD | Line::MREQ, + Line::CLK | Line::RD | Line::MREQ, + }; + return states; + } + + case Operation::ReadWait: { + static constexpr uint8_t states[] = { + Line::MREQ | Line::RD, + Line::CLK | Line::MREQ | Line::RD, + Line::MREQ | Line::RD, + Line::CLK | Line::MREQ | Line::RD, + Line::MREQ | Line::RD, + Line::CLK | Line::MREQ | Line::RD, + }; + return states; + } + + case Operation::Read: { + static constexpr uint8_t states[] = { + Line::MREQ | Line::RD, + Line::CLK | Line::MREQ | Line::RD, + 0, + }; + return states; + } + + // + // Write cycle. + // + + case Operation::WriteStart: { + static constexpr uint8_t states[] = { + Line::CLK, + Line::MREQ, + Line::CLK | Line::MREQ, + }; + return states; + } + + case Operation::WriteWait: { + static constexpr uint8_t states[] = { + Line::MREQ, + Line::CLK | Line::MREQ, + Line::MREQ, + Line::CLK | Line::MREQ, + Line::MREQ, + Line::CLK | Line::MREQ, + }; + return states; + } + + case Operation::Write: { + static constexpr uint8_t states[] = { + Line::MREQ | Line::WR, + Line::CLK | Line::MREQ | Line::WR, + 0, + }; + return states; + } + + // + // Input cycle. + // + + case Operation::InputStart: { + static constexpr uint8_t states[] = { + Line::CLK, + 0, + Line::CLK | Line::IOREQ | Line::RD, + }; + return states; + } + + case Operation::InputWait: { + static constexpr uint8_t states[] = { + Line::IOREQ | Line::RD, + Line::CLK | Line::IOREQ | Line::RD, + }; + return states; + } + + case Operation::Input: { + static constexpr uint8_t states[] = { + Line::IOREQ | Line::RD, + Line::CLK | Line::IOREQ | Line::RD, + 0, + }; + return states; + } + + // + // Output cycle. + // + + case Operation::OutputStart: { + static constexpr uint8_t states[] = { + Line::CLK, + 0, + Line::CLK | Line::IOREQ | Line::WR, + }; + return states; + } + + case Operation::OutputWait: { + static constexpr uint8_t states[] = { + Line::IOREQ | Line::WR, + Line::CLK | Line::IOREQ | Line::WR, + }; + return states; + } + + case Operation::Output: { + static constexpr uint8_t states[] = { + Line::IOREQ | Line::WR, + Line::CLK | Line::IOREQ | Line::WR, + 0, + }; + return states; + } + + // + // TODO: Interrupt acknowledge. + // + + // + // Bus acknowldge. + // + + case Operation::BusAcknowledge: { + static constexpr uint8_t states[] = { + Line::CLK | Line::BUSACK, + Line::BUSACK, + }; + return states; + } + + // + // Internal. + // + + case Operation::Internal: { + static constexpr uint8_t states[] = { + Line::CLK, 0, + Line::CLK, 0, + Line::CLK, 0, + Line::CLK, 0, + Line::CLK, 0, + }; + return states; + } + + default: break; + } + + return nullptr; + } + PartialMachineCycle(const PartialMachineCycle &rhs) noexcept; PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept; PartialMachineCycle() noexcept;