// // 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 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]; } } @end