From 32aebfebe0ed1ed3a66eeb130032fb1371603d22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Apr 2021 23:02:40 -0400 Subject: [PATCH 01/41] Starts spelling out meaning of the Z80's partial machine cycles. --- Processors/Z80/Z80.hpp | 132 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 777fd1cdc..0917212b0 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -75,20 +75,31 @@ struct PartialMachineCycle { Output, Interrupt, + // The two-cycle refresh part of an M1 cycle. Refresh, Internal, BusAcknowledge, + // A WAIT-induced wait state within an M1 cycle. ReadOpcodeWait, + // A WAIT-induced wait state within a read cycle. ReadWait, + // A WAIT-induced wait state within a write cycle. WriteWait, + // A WAIT-induced wait state within an input cycle. InputWait, + // A WAIT-induced wait state within an output cycle. OutputWait, + // A WAIT-induced 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, + InputStart, OutputStart, InterruptStart, @@ -125,6 +136,127 @@ 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, + }; + + /// @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. + 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::RFSH | Line::MREQ, + Line::CLK | Line::RFSH | Line::MREQ, + Line::RFSH, + Line::CLK | Line::RFSH, + Line::RFSH, + }; + return states; + } + + // + // Standard read/write 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; + } + + // TODO: write, input, output, bus acknowledge, interrupt acknowledge + + default: break; + } + + static constexpr uint8_t none[] = {}; + return none; + } + + /// @returns The state of the MREQ line during this partial machine cycle. +// HalfCycles *mreq_spans() const { +// return nullptr; +// switch(operation) { +// default: return LineOutput(); +// +// case Operation::ReadOpcodeStart: +// case Operation::ReadStart: +// case Operation::WriteStart: +// return LineOutput(false, HalfCycles(1)); +// +// case Operation::ReadOpcode: +// return LineOutput(true, HalfCycles(1)); +// +// case Operation::Read: +// case Operation::Write: +// return LineOutput(true, HalfCycles(2)); +// +// case Operation::ReadOpcodeWait: +// case Operation::ReadWait: +// case Operation::WriteWait: +// return LineOutput(true); +// } +// } + PartialMachineCycle(const PartialMachineCycle &rhs) noexcept; PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept; PartialMachineCycle() noexcept; From 294280a94ef6a86fc069adf844f73431c2385b58 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 2 Apr 2021 07:34:06 -0400 Subject: [PATCH 02/41] Spells out everything except interrupt acknowledge. --- Processors/Z80/Z80.hpp | 163 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 14 deletions(-) diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 0917212b0..f7b5193b6 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -68,40 +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. + /// 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-induced wait state within an M1 cycle. + /// A wait state within an M1 cycle. ReadOpcodeWait, - // A WAIT-induced wait state within a read cycle. + /// A wait state within a read cycle. ReadWait, - // A WAIT-induced wait state within a write cycle. + /// A wait state within a write cycle. WriteWait, - // A WAIT-induced wait state within an input cycle. + /// A wait state within an input cycle. InputWait, - // A WAIT-induced wait state within an output cycle. + /// A wait state within an output cycle. OutputWait, - // A WAIT-induced wait state within an interrupt acknowledge cycle. + /// 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. + /// 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. + /// 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. + /// 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. @@ -147,11 +157,13 @@ struct PartialMachineCycle { 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. + /// bit set means line active, bit clear means line inactive. For the CLK line set means high. const uint8_t *bus_state() const { switch(operation) { @@ -190,7 +202,7 @@ struct PartialMachineCycle { } // - // Standard read/write cycle. + // Read cycle. // case Operation::ReadStart: { @@ -223,7 +235,130 @@ struct PartialMachineCycle { return states; } - // TODO: write, input, output, bus acknowledge, interrupt acknowledge + // + // 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; } From 1be88a5308df1bea68eedb79e64fbd80c20f9da4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 2 Apr 2021 07:39:22 -0400 Subject: [PATCH 03/41] Remove first draft. --- Processors/Z80/Z80.hpp | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index f7b5193b6..38d792fc6 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -367,31 +367,6 @@ struct PartialMachineCycle { return none; } - /// @returns The state of the MREQ line during this partial machine cycle. -// HalfCycles *mreq_spans() const { -// return nullptr; -// switch(operation) { -// default: return LineOutput(); -// -// case Operation::ReadOpcodeStart: -// case Operation::ReadStart: -// case Operation::WriteStart: -// return LineOutput(false, HalfCycles(1)); -// -// case Operation::ReadOpcode: -// return LineOutput(true, HalfCycles(1)); -// -// case Operation::Read: -// case Operation::Write: -// return LineOutput(true, HalfCycles(2)); -// -// case Operation::ReadOpcodeWait: -// case Operation::ReadWait: -// case Operation::WriteWait: -// return LineOutput(true); -// } -// } - PartialMachineCycle(const PartialMachineCycle &rhs) noexcept; PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept; PartialMachineCycle() noexcept; From 25b8c4c06299a529422fb6989b8cdb3a7f18a644 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Apr 2021 21:04:44 -0400 Subject: [PATCH 04/41] Provide clearer failure case. --- Processors/Z80/Z80.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 38d792fc6..39a7abd77 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -363,8 +363,7 @@ struct PartialMachineCycle { default: break; } - static constexpr uint8_t none[] = {}; - return none; + return nullptr; } PartialMachineCycle(const PartialMachineCycle &rhs) noexcept; From 67fd6787a6b54a57b9002ef034037063c44661b1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 7 Apr 2021 21:57:40 -0400 Subject: [PATCH 05/41] Builds what I think I need to validate Z80 address, MREQ, IOREQ and RFSH. --- .../Clock Signal.xcodeproj/project.pbxproj | 4 + .../Clock SignalTests/Z80ContentionTests.mm | 194 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm 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..12dc0be6a --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -0,0 +1,194 @@ +// +// 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 = 0; + +struct CapturingZ80: public CPU::Z80::BusHandler { + + CapturingZ80(const std::initializer_list &code) : z80_(*this) { + // Take a copy of the code. + std::copy(code.begin(), code.end(), ram_); + + // Skip the three cycles the Z80 spends on a reset, and + // purge them from the record. + run_for(3); + bus_records_.clear(); + + // Set the refresh address to the EE page. + z80_.set_value_of_register(CPU::Z80::Register::I, 0xe0); + } + + void run_for(int cycles) { + z80_.run_for(HalfCycles(Cycles(cycles))); + } + + /// 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]; + } + + 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]; + + 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; +}; + +/*! + 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. + // + // I think the source I'm using also implicitly assumes that refresh + // addresses are outside of the contended area, and doesn't check them. + // So unlike the actual ULA I'm also ignoring any address while refresh + // is asserted. + int count = -1; + uint16_t address = 0; + auto contention = contentions.begin(); + + const auto bus_records = z80.cycle_records(); + for(const auto &record: bus_records) { + ++count; + + if( + !count || // i.e. is at start. + (&record == &bus_records.back()) || // i.e. is at end. + !(record.mreq || record.refresh) // i.e. beginning of a new contention. + ) { + if(count) { + XCTAssertNotEqual(contention, contentions.end()); + XCTAssertEqual(contention->address, address); + XCTAssertEqual(contention->length, count); + ++contention; + } + + count = 1; + address = record.address; + } + } + + XCTAssertEqual(contention, contentions.end()); +} + +/*! + 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. + int count = -1; + uint16_t address = 0; + auto contention = contentions.begin(); + + const auto bus_records = z80.bus_records(); + + for(size_t c = 0; c < bus_records.size(); c += 2) { + const bool is_leading_edge = !bus_records[c].mreq && bus_records[c+1].mreq && !bus_records[c].refresh; + + ++count; + if( + !count || // i.e. is at start. + (c == bus_records.size() - 2) || // i.e. is at end. + is_leading_edge // i.e. beginning of a new contention. + ) { + if(count) { + XCTAssertNotEqual(contention, contentions.end()); + XCTAssertEqual(contention->address, address); + XCTAssertEqual(contention->length, count); + ++contention; + } + + count = 1; + address = bus_records[c].address; + } + } + + XCTAssertEqual(contention, contentions.end()); +} + +// MARK: - Opcode tests. + +- (void)testNOP { + CapturingZ80 z80({0x00}); + z80.run_for(4); + + [self validate48Contention:{{initial_pc, 4}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}} z80:z80]; +} + +@end From cd787486d2b15d6e928aa5cfe5097ff6916ff147 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 7 Apr 2021 22:07:52 -0400 Subject: [PATCH 06/41] Tests all of the single-byte, no-access opcodes. --- .../Clock SignalTests/Z80ContentionTests.mm | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 12dc0be6a..2da9ad1e3 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -183,12 +183,54 @@ struct ContentionCheck { // MARK: - Opcode tests. -- (void)testNOP { - CapturingZ80 z80({0x00}); - z80.run_for(4); +- (void)testSimpleSingleBytes { + for(uint8_t opcode : { + 0x00, // NOP - [self validate48Contention:{{initial_pc, 4}} z80:z80]; - [self validatePlus3Contention:{{initial_pc, 4}} z80:z80]; + // 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) + }) { + CapturingZ80 z80({opcode}); + z80.run_for(4); + + [self validate48Contention:{{initial_pc, 4}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}} z80:z80]; + } } @end From 57a7e0834f0dd34880655fc036927c40ce8635a0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 19:21:35 -0400 Subject: [PATCH 07/41] Corrects sampling of MREQ. --- OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm | 2 +- Processors/Z80/Z80.hpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 2da9ad1e3..c18497c3a 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -128,7 +128,7 @@ struct ContentionCheck { if( !count || // i.e. is at start. (&record == &bus_records.back()) || // i.e. is at end. - !(record.mreq || record.refresh) // i.e. beginning of a new contention. + !record.mreq // i.e. beginning of a new contention. ) { if(count) { XCTAssertNotEqual(contention, contentions.end()); diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 39a7abd77..21ad99563 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -164,6 +164,8 @@ struct PartialMachineCycle { /// @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) { @@ -191,10 +193,10 @@ struct PartialMachineCycle { case Operation::Refresh: { static constexpr uint8_t states[] = { - Line::CLK | Line::RFSH, - Line::RFSH | Line::MREQ, Line::CLK | Line::RFSH | Line::MREQ, Line::RFSH, + Line::CLK | Line::RFSH | Line::MREQ, + Line::RFSH | Line::MREQ, Line::CLK | Line::RFSH, Line::RFSH, }; From 818655a9b655db5cdcf6f932530ca939de62735c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 20:01:46 -0400 Subject: [PATCH 08/41] Starts on two-bus-cycle instructions, correcting validators. --- .../Clock SignalTests/Z80ContentionTests.mm | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index c18497c3a..22020f11a 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -16,7 +16,7 @@ static constexpr uint16_t initial_pc = 0; struct CapturingZ80: public CPU::Z80::BusHandler { - CapturingZ80(const std::initializer_list &code) : z80_(*this) { + template CapturingZ80(const Collection &code) : z80_(*this) { // Take a copy of the code. std::copy(code.begin(), code.end(), ram_); @@ -97,8 +97,7 @@ struct CapturingZ80: public CPU::Z80::BusHandler { observed by ZX Spectrum users in the software-side documentation of contended memory timings. */ -@implementation Z80ContentionTests { -} +@implementation Z80ContentionTests struct ContentionCheck { uint16_t address; @@ -112,31 +111,28 @@ struct ContentionCheck { - (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. - // - // I think the source I'm using also implicitly assumes that refresh - // addresses are outside of the contended area, and doesn't check them. - // So unlike the actual ULA I'm also ignoring any address while refresh - // is asserted. - int count = -1; - uint16_t address = 0; auto contention = contentions.begin(); - const auto bus_records = z80.cycle_records(); + + int count = 0; + uint16_t address = bus_records.front().address; for(const auto &record: bus_records) { ++count; + const bool is_final = &record == &bus_records.back(); if( - !count || // i.e. is at start. - (&record == &bus_records.back()) || // i.e. is at end. - !record.mreq // i.e. beginning of a new contention. + &record != &bus_records.front() && ( // i.e. not at front. + is_final || + !record.mreq // i.e. beginning of a new contention. + ) ) { - if(count) { - XCTAssertNotEqual(contention, contentions.end()); - XCTAssertEqual(contention->address, address); - XCTAssertEqual(contention->length, count); - ++contention; - } + XCTAssertNotEqual(contention, contentions.end()); + XCTAssertEqual(contention->address, address); + XCTAssertEqual(contention->length, count - !is_final); // Subtract 1 if not at end, because in that case this cycle + // is one after the previous group ended. + + ++contention; count = 1; address = record.address; } @@ -151,27 +147,28 @@ struct ContentionCheck { */ - (void)validatePlus3Contention:(const std::initializer_list &)contentions z80:(const CapturingZ80 &)z80 { // +3 contention logic: triggered by the leading edge of MREQ, sans refresh. - int count = -1; - uint16_t address = 0; auto contention = contentions.begin(); - const auto bus_records = z80.bus_records(); - for(size_t c = 0; c < bus_records.size(); c += 2) { - const bool is_leading_edge = !bus_records[c].mreq && bus_records[c+1].mreq && !bus_records[c].refresh; + int count = 0; + uint16_t address = bus_records.front().address; + for(size_t c = 0; c < bus_records.size(); c += 2) { ++count; + + const bool is_leading_edge = !bus_records[c].mreq && bus_records[c+1].mreq && !bus_records[c].refresh; + const bool is_final = c == bus_records.size() - 2; if( - !count || // i.e. is at start. - (c == bus_records.size() - 2) || // i.e. is at end. - is_leading_edge // i.e. beginning of a new contention. + c && ( // i.e. not at front. + is_final || + is_leading_edge // i.e. beginning of a new contention. + ) ) { - if(count) { - XCTAssertNotEqual(contention, contentions.end()); - XCTAssertEqual(contention->address, address); - XCTAssertEqual(contention->length, count); - ++contention; - } + XCTAssertNotEqual(contention, contentions.end()); + + XCTAssertEqual(contention->address, address); + XCTAssertEqual(contention->length, count - !is_final); // See validate48Contention for exposition. + ++contention; count = 1; address = bus_records[c].address; @@ -183,7 +180,7 @@ struct ContentionCheck { // MARK: - Opcode tests. -- (void)testSimpleSingleBytes { +- (void)testSimpleOneBytes { for(uint8_t opcode : { 0x00, // NOP @@ -225,7 +222,8 @@ struct ContentionCheck { 0x0f, // RRCA 0xe9, // JP (HL) }) { - CapturingZ80 z80({opcode}); + const std::initializer_list opcodes = {opcode}; + CapturingZ80 z80(opcodes); z80.run_for(4); [self validate48Contention:{{initial_pc, 4}} z80:z80]; @@ -233,4 +231,21 @@ struct ContentionCheck { } } +- (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}, + }) { + 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]; + } +} + @end From ee989ab762f9a0ef3dcbed57080b293aa8cecfb6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 20:13:52 -0400 Subject: [PATCH 09/41] Fills in the rest of the simple two-byte instructions. --- .../Clock SignalTests/Z80ContentionTests.mm | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 22020f11a..ceb31e8f4 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -236,9 +236,85 @@ struct ContentionCheck { // 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) + // 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, 0x04}, {0xed, 0x0c}, {0xed, 0x14}, {0xed, 0x1c}, + {0xed, 0x24}, {0xed, 0x2c}, {0xed, 0x34}, {0xed, 0x3c}, + + // IM 0/1/2 + {0xed, 0x06}, {0xed, 0x0e}, {0xed, 0x16}, {0xed, 0x1e}, + {0xed, 0x26}, {0xed, 0x2e}, {0xed, 0x36}, {0xed, 0x3e}, }) { CapturingZ80 z80(sequence); z80.run_for(8); From f74fa06f2d2fc5149c10a9afe6a0acb82fee7cb3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 20:28:55 -0400 Subject: [PATCH 10/41] Introduces failing test for LD [A/I/R], [A/I/R]. --- .../Clock SignalTests/Z80ContentionTests.mm | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index ceb31e8f4..5158f0b79 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -12,21 +12,24 @@ namespace { -static constexpr uint16_t initial_pc = 0; +static constexpr uint16_t initial_pc = 0x0000; +static constexpr uint16_t initial_ir = 0xe000; 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 refresh address to the EE page. + // 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); } void run_for(int cycles) { @@ -63,6 +66,8 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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); @@ -83,6 +88,7 @@ struct CapturingZ80: public CPU::Z80::BusHandler { private: CPU::Z80::Processor z80_; uint8_t ram_[65536]; + uint16_t code_length_ = 0; std::vector bus_records_; }; @@ -116,14 +122,14 @@ struct ContentionCheck { int count = 0; uint16_t address = bus_records.front().address; - for(const auto &record: bus_records) { + for(size_t c = 0; c < bus_records.size(); c++) { ++count; - const bool is_final = &record == &bus_records.back(); + const bool is_final = c == bus_records.size() - 1; if( - &record != &bus_records.front() && ( // i.e. not at front. + c && ( // i.e. not at front. is_final || - !record.mreq // i.e. beginning of a new contention. + !bus_records[c].mreq // i.e. beginning of a new contention. ) ) { XCTAssertNotEqual(contention, contentions.end()); @@ -134,7 +140,7 @@ struct ContentionCheck { ++contention; count = 1; - address = record.address; + address = bus_records[c].address; } } @@ -166,8 +172,8 @@ struct ContentionCheck { ) { XCTAssertNotEqual(contention, contentions.end()); - XCTAssertEqual(contention->address, address); - XCTAssertEqual(contention->length, count - !is_final); // See validate48Contention for exposition. + XCTAssertEqual(contention->address, address, "Address mismatch at half-cycle %zu", c); + XCTAssertEqual(contention->length, count - !is_final, "Length mismatch at half-cycle %zu", c); // See validate48Contention for exposition. ++contention; count = 1; @@ -175,7 +181,7 @@ struct ContentionCheck { } } - XCTAssertEqual(contention, contentions.end()); + XCTAssertEqual(contention, contentions.end(), "Not all supplied contentions used"); } // MARK: - Opcode tests. @@ -324,4 +330,18 @@ struct ContentionCheck { } } +- (void)testAIR { + for(const auto &sequence : std::vector>{ + {0xed, 0x17}, // LD A, I + {0xed, 0x1f}, // LD A, R + {0xed, 0x07}, // LD I, A + {0xed, 0x0f}, // LD R, A + }) { + CapturingZ80 z80(sequence); + z80.run_for(9); + + [self validate48Contention:{{initial_pc, 4}, {initial_pc+1, 4}, {initial_ir+2, 1}} z80:z80]; + [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 5}} z80:z80]; + } +} @end From 73fbd89c85aeac42be96e34572af80f90f2ab927 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 22:09:33 -0400 Subject: [PATCH 11/41] Correct opcodes, ability to terminate on a single-cycle contention. --- .../Clock SignalTests/Z80ContentionTests.mm | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 5158f0b79..13b99fb67 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -125,18 +125,14 @@ struct ContentionCheck { for(size_t c = 0; c < bus_records.size(); c++) { ++count; - const bool is_final = c == bus_records.size() - 1; if( - c && ( // i.e. not at front. - is_final || - !bus_records[c].mreq // i.e. beginning of a new contention. - ) + c && // i.e. not at front. + !bus_records[c].mreq // i.e. beginning of a new contention. ) { XCTAssertNotEqual(contention, contentions.end()); XCTAssertEqual(contention->address, address); - XCTAssertEqual(contention->length, count - !is_final); // Subtract 1 if not at end, because in that case this cycle - // is one after the previous group ended. + XCTAssertEqual(contention->length, count - 1); ++contention; count = 1; @@ -144,6 +140,9 @@ struct ContentionCheck { } } + XCTAssertEqual(contention->address, address); + XCTAssertEqual(contention->length, count); + ++contention; XCTAssertEqual(contention, contentions.end()); } @@ -163,17 +162,14 @@ struct ContentionCheck { ++count; const bool is_leading_edge = !bus_records[c].mreq && bus_records[c+1].mreq && !bus_records[c].refresh; - const bool is_final = c == bus_records.size() - 2; if( - c && ( // i.e. not at front. - is_final || - is_leading_edge // i.e. beginning of a new contention. - ) + c && // i.e. not at front. + is_leading_edge // i.e. beginning of a new contention. ) { XCTAssertNotEqual(contention, contentions.end()); XCTAssertEqual(contention->address, address, "Address mismatch at half-cycle %zu", c); - XCTAssertEqual(contention->length, count - !is_final, "Length mismatch at half-cycle %zu", c); // See validate48Contention for exposition. + XCTAssertEqual(contention->length, count - 1, "Length mismatch at half-cycle %zu", c); ++contention; count = 1; @@ -181,6 +177,10 @@ struct ContentionCheck { } } + XCTAssertEqual(contention->address, address, "Address mismatch at end"); + XCTAssertEqual(contention->length, count, "Length mismatch at end"); + + ++contention; XCTAssertEqual(contention, contentions.end(), "Not all supplied contentions used"); } @@ -315,12 +315,12 @@ struct ContentionCheck { {0xcb, 0xfc}, {0xcb, 0xfd}, {0xcb, 0xff}, // NEG - {0xed, 0x04}, {0xed, 0x0c}, {0xed, 0x14}, {0xed, 0x1c}, - {0xed, 0x24}, {0xed, 0x2c}, {0xed, 0x34}, {0xed, 0x3c}, + {0xed, 0x44}, {0xed, 0x4c}, {0xed, 0x54}, {0xed, 0x5c}, + {0xed, 0x64}, {0xed, 0x6c}, {0xed, 0x74}, {0xed, 0x7c}, // IM 0/1/2 - {0xed, 0x06}, {0xed, 0x0e}, {0xed, 0x16}, {0xed, 0x1e}, - {0xed, 0x26}, {0xed, 0x2e}, {0xed, 0x36}, {0xed, 0x3e}, + {0xed, 0x46}, {0xed, 0x4e}, {0xed, 0x56}, {0xed, 0x5e}, + {0xed, 0x66}, {0xed, 0x6e}, {0xed, 0x66}, {0xed, 0x6e}, }) { CapturingZ80 z80(sequence); z80.run_for(8); @@ -332,15 +332,15 @@ struct ContentionCheck { - (void)testAIR { for(const auto &sequence : std::vector>{ - {0xed, 0x17}, // LD A, I - {0xed, 0x1f}, // LD A, R - {0xed, 0x07}, // LD I, A - {0xed, 0x0f}, // LD R, A + {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+2, 1}} z80:z80]; + [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]; } } From 50f53f7d97cc6b82a6a88b07803eb99dd0f928c8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 22:14:53 -0400 Subject: [PATCH 12/41] Adds INC/DEC rr and LD SP, HL tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 13b99fb67..81cb8c2d4 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -344,4 +344,26 @@ struct ContentionCheck { [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 5}} z80:z80]; } } + +- (void)testINCDEC16 { + for(uint8_t opcode : { + // INC rr + 0x03, 0x13, 0x23, 0x33, + + // DEC rr + 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]; + } +} + + @end From 29cf80339a37337e40a38915af3e134c46bc0509 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 22:15:03 -0400 Subject: [PATCH 13/41] Corrects too-short buffer. --- Processors/Z80/Z80.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 21ad99563..fd8eaad5d 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -199,6 +199,8 @@ struct PartialMachineCycle { Line::RFSH | Line::MREQ, Line::CLK | Line::RFSH, Line::RFSH, + Line::CLK | Line::RFSH, + Line::RFSH, }; return states; } From 9e506c3206381357e91bfdbc5eb6e7d363f254e4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 22:19:22 -0400 Subject: [PATCH 14/41] Adds failing ADD hl, dd test. --- .../Clock SignalTests/Z80ContentionTests.mm | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 81cb8c2d4..0881e622e 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -347,10 +347,10 @@ struct ContentionCheck { - (void)testINCDEC16 { for(uint8_t opcode : { - // INC rr + // INC dd 0x03, 0x13, 0x23, 0x33, - // DEC rr + // DEC dd 0x0b, 0x1b, 0x2b, 0x3b, // LD SP, HL @@ -365,5 +365,27 @@ struct ContentionCheck { } } +- (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]; + } +} @end From eacffa49f5387ac1ebf5833677ceb3b088acf6d0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 22:22:26 -0400 Subject: [PATCH 15/41] Exposes IR during 'internal' operations. --- Processors/Z80/Implementation/Z80Storage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index b41d8715e..b63c8392c 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -62,7 +62,7 @@ ProcessorStorage::ProcessorStorage() { #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), &ir_.full, nullptr, false}} /// A sequence is a series of micro-ops that ends in a move-to-next-program operation. #define Sequence(...) { __VA_ARGS__, {MicroOp::MoveToNextProgram} } From 818a4dff25f1b18e66af8dbb89889340f7c56d46 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 8 Apr 2021 22:23:15 -0400 Subject: [PATCH 16/41] Corrects ADD HL, dd test. Or, at least, likely corrects. The bus cycle breakdown in the Z80 data sheet implies these accesses should come after completion of the refresh cycle, not during its long tail, so I think +1 is correct. --- .../Mac/Clock SignalTests/Z80ContentionTests.mm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 0881e622e..49614d2ee 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -376,13 +376,13 @@ struct ContentionCheck { [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}, + {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, 11}} z80:z80]; } From 87202a2a270d21aaea9f1a0f9eb379fccd378394 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 9 Apr 2021 18:32:03 -0400 Subject: [PATCH 17/41] Add two further tests, add checking of collected data size for all tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 49614d2ee..9780ade5c 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -34,6 +34,7 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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, @@ -388,4 +389,48 @@ struct ContentionCheck { } } +- (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+2, 1}, + {initial_ir+2, 1}, + {initial_ir+2, 1}, + {initial_ir+2, 1}, + {initial_ir+2, 1}, + {initial_ir+2, 1}, + {initial_ir+2, 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]; + } +} + @end From ce3d2913bf1c9f9f147bddfe4f1c2e450418642d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 9 Apr 2021 20:38:17 -0400 Subject: [PATCH 18/41] Advances to 9 source table rows tested out of 37. --- .../Clock SignalTests/Z80ContentionTests.mm | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 9780ade5c..1ca0707da 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -14,6 +14,7 @@ namespace { static constexpr uint16_t initial_pc = 0x0000; static constexpr uint16_t initial_ir = 0xe000; +static constexpr uint16_t initial_bc_de_hl = 0xabcd; struct CapturingZ80: public CPU::Z80::BusHandler { @@ -30,6 +31,11 @@ struct CapturingZ80: public CPU::Z80::BusHandler { // 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); } void run_for(int cycles) { @@ -420,7 +426,7 @@ struct ContentionCheck { // LD r, n 0x06, 0x0e, 0x16, 0x1e, 0x26, 0x2e, 0x3e, - 0xc6, // ADD a, n + 0xc6, // ADD A, n 0xce, // ADC A, n 0xde, // SBC A, n }) { @@ -433,4 +439,26 @@ struct ContentionCheck { } } +- (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]; + } +} + @end From b09c5538c636297fe9be9e29a8d6ad8b21362025 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 9 Apr 2021 21:28:35 -0400 Subject: [PATCH 19/41] Adds failing test for simple (ii+n) tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 1ca0707da..4e2b3ea9f 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -15,6 +15,7 @@ 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; struct CapturingZ80: public CPU::Z80::BusHandler { @@ -36,6 +37,10 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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); } void run_for(int cycles) { @@ -461,4 +466,61 @@ struct ContentionCheck { } } +- (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]; + } +} + @end From e0736435f8e30622ce3c4d7caf14db8dd9a0aa4c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Apr 2021 12:00:53 -0400 Subject: [PATCH 20/41] Makes assumption that the address bus just holds its value during an internal operation. --- .../Clock SignalTests/Z80ContentionTests.mm | 28 +++++++++---------- .../Z80/Implementation/Z80Implementation.hpp | 4 +++ Processors/Z80/Implementation/Z80Storage.cpp | 2 +- Processors/Z80/Implementation/Z80Storage.hpp | 2 ++ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 4e2b3ea9f..712030334 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -388,13 +388,13 @@ struct ContentionCheck { [self validate48Contention:{ {initial_pc, 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}, + {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]; } @@ -414,13 +414,13 @@ struct ContentionCheck { [self validate48Contention:{ {initial_pc, 4}, {initial_pc+1, 4}, - {initial_ir+2, 1}, - {initial_ir+2, 1}, - {initial_ir+2, 1}, - {initial_ir+2, 1}, - {initial_ir+2, 1}, - {initial_ir+2, 1}, - {initial_ir+2, 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}, + {initial_ir+1, 1}, } z80:z80]; [self validatePlus3Contention:{{initial_pc, 4}, {initial_pc+1, 11}} z80:z80]; } diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 677d55d69..cbb4acc89 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -82,6 +82,10 @@ 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; diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index b63c8392c..7aa1af49f 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -62,7 +62,7 @@ ProcessorStorage::ProcessorStorage() { #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), &ir_.full, nullptr, false}} +#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}} /// A sequence is a series of micro-ops that ends in a move-to-next-program operation. #define Sequence(...) { __VA_ARGS__, {MicroOp::MoveToNextProgram} } diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index fb0f6bcdc..c682a259c 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -149,6 +149,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 { From 400f54e508735082f5319b2d721dd6b943a0c946 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Apr 2021 12:04:48 -0400 Subject: [PATCH 21/41] Introduces failing test for bit b, (hl). --- .../Clock SignalTests/Z80ContentionTests.mm | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 712030334..9033562b0 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -523,4 +523,28 @@ struct ContentionCheck { } } +- (void)testBITbhl { + constexpr uint8_t offset = 0x10; + for(const auto &sequence : std::vector>{ + {0xcb, 0x46, offset}, {0xcb, 0x4e, offset}, + {0xcb, 0x56, offset}, {0xcb, 0x5e, offset}, + {0xcb, 0x66, offset}, {0xcb, 0x6e, offset}, + {0xcb, 0x76, offset}, {0xcb, 0x7e, offset}, + }) { + 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]; + } +} @end From b397059d5ed76baa67416a1eff3f62e2ccedd6cc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Apr 2021 17:54:20 -0400 Subject: [PATCH 22/41] Moves read time in Read4Pre. --- Processors/Z80/Implementation/Z80Storage.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index 7aa1af49f..69f1cc8e0 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -48,13 +48,15 @@ ProcessorStorage::ProcessorStorage() { // Compound bus operations, as micro-ops +#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}} + // 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; +// Read4Pre is a four-cycle read that has to do something after reading: 1.5 cycles, then check the wait line, then 1.5 cycles, then a 1-cycle internal operation; // 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 Read4Pre(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(2) #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)) @@ -62,7 +64,6 @@ ProcessorStorage::ProcessorStorage() { #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), &last_address_bus_, nullptr, false}} /// A sequence is a series of micro-ops that ends in a move-to-next-program operation. #define Sequence(...) { __VA_ARGS__, {MicroOp::MoveToNextProgram} } From 070e359d82878b29546d225e5506d229668620ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Apr 2021 18:00:23 -0400 Subject: [PATCH 23/41] Introduces failing test for BIT b, (ii+n). --- .../Clock SignalTests/Z80ContentionTests.mm | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 9033562b0..1cdce76d9 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -524,12 +524,11 @@ struct ContentionCheck { } - (void)testBITbhl { - constexpr uint8_t offset = 0x10; for(const auto &sequence : std::vector>{ - {0xcb, 0x46, offset}, {0xcb, 0x4e, offset}, - {0xcb, 0x56, offset}, {0xcb, 0x5e, offset}, - {0xcb, 0x66, offset}, {0xcb, 0x6e, offset}, - {0xcb, 0x76, offset}, {0xcb, 0x7e, offset}, + {0xcb, 0x46}, {0xcb, 0x4e}, + {0xcb, 0x56}, {0xcb, 0x5e}, + {0xcb, 0x66}, {0xcb, 0x6e}, + {0xcb, 0x76}, {0xcb, 0x7e}, }) { CapturingZ80 z80(sequence); z80.run_for(12); @@ -547,4 +546,43 @@ struct ContentionCheck { } 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]; + } +} + @end From 47c5a243aaff02ced2454e6bcfe5ffa6dde8653d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Apr 2021 21:32:42 -0400 Subject: [PATCH 24/41] Restructures, the better to explore errors. --- .../Clock SignalTests/Z80ContentionTests.mm | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 1cdce76d9..13800c8e8 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -122,6 +122,25 @@ struct ContentionCheck { int length; }; +- (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); + + 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. @@ -129,11 +148,12 @@ struct ContentionCheck { - (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. - auto contention = contentions.begin(); const auto bus_records = z80.cycle_records(); + std::vector found_contentions; int count = 0; uint16_t address = bus_records.front().address; + for(size_t c = 0; c < bus_records.size(); c++) { ++count; @@ -141,21 +161,20 @@ struct ContentionCheck { c && // i.e. not at front. !bus_records[c].mreq // i.e. beginning of a new contention. ) { - XCTAssertNotEqual(contention, contentions.end()); + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count - 1; - XCTAssertEqual(contention->address, address); - XCTAssertEqual(contention->length, count - 1); - - ++contention; count = 1; address = bus_records[c].address; } } - XCTAssertEqual(contention->address, address); - XCTAssertEqual(contention->length, count); - ++contention; - XCTAssertEqual(contention, contentions.end()); + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count; + + [self compareExpectedContentions:contentions found:found_contentions label:"48kb"]; } /*! @@ -164,8 +183,8 @@ struct ContentionCheck { */ - (void)validatePlus3Contention:(const std::initializer_list &)contentions z80:(const CapturingZ80 &)z80 { // +3 contention logic: triggered by the leading edge of MREQ, sans refresh. - auto contention = contentions.begin(); const auto bus_records = z80.bus_records(); + std::vector found_contentions; int count = 0; uint16_t address = bus_records.front().address; @@ -178,22 +197,20 @@ struct ContentionCheck { c && // i.e. not at front. is_leading_edge // i.e. beginning of a new contention. ) { - XCTAssertNotEqual(contention, contentions.end()); - - XCTAssertEqual(contention->address, address, "Address mismatch at half-cycle %zu", c); - XCTAssertEqual(contention->length, count - 1, "Length mismatch at half-cycle %zu", c); - ++contention; + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count - 1; count = 1; address = bus_records[c].address; } } - XCTAssertEqual(contention->address, address, "Address mismatch at end"); - XCTAssertEqual(contention->length, count, "Length mismatch at end"); + found_contentions.emplace_back(); + found_contentions.back().address = address; + found_contentions.back().length = count; - ++contention; - XCTAssertEqual(contention, contentions.end(), "Not all supplied contentions used"); + [self compareExpectedContentions:contentions found:found_contentions label:"+3"]; } // MARK: - Opcode tests. From 015556cc91a87efa86a5b161edbc586a6234db83 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 11 Apr 2021 10:26:14 -0400 Subject: [PATCH 25/41] Switch (ii+n) to Read4Pre. --- Processors/Z80/Implementation/Z80Storage.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index 69f1cc8e0..f880298b6 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -154,14 +154,14 @@ 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_}) + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {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}) @@ -537,10 +537,10 @@ void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, in }; const MicroOp short_fetch_decode_execute[] = { BusOp(ReadOpcodeStart()), - BusOp(ReadOpcodeWait(false)), BusOp(ReadOpcodeWait(true)), + BusOp(ReadOpcodeWait(false)), BusOp(ReadOpcodeEnd()), - { MicroOp::DecodeOperation } + { 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(); From 9cde7c12baaed7d9cf77895e20e43c3fca2f1034 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 11 Apr 2021 22:50:24 -0400 Subject: [PATCH 26/41] Shifts responsibility for refresh into the fetch-decode-execute sequence. --- .../Z80/Implementation/Z80Implementation.hpp | 11 +- Processors/Z80/Implementation/Z80Storage.cpp | 343 +++++++++--------- Processors/Z80/Implementation/Z80Storage.hpp | 2 +- 3 files changed, 176 insertions(+), 180 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index cbb4acc89..12f637ca5 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -92,18 +92,15 @@ template < class T, 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); + 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; @@ -931,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 f880298b6..e2b23d329 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -20,7 +20,7 @@ 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) @@ -48,32 +48,26 @@ ProcessorStorage::ProcessorStorage() { // Compound bus operations, as micro-ops -#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}} +#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}} // 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 1.5 cycles, then a 1-cycle internal operation; // 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(ReadEnd(addr, val)), InternalOperation(2) -#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 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(ReadEnd(addr, val)), InternalOperation(2) +#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 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 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 @@ -104,44 +98,44 @@ ProcessorStorage::ProcessorStorage() { #define Pop7(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read4(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()}, Read4Inc(pc_, memptr_.halves.high), 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(), Read3(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(), Read3(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(), Read4Pre(INDEX_ADDR(), temp8_), {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(), Read4Pre(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x)) +#define RMWI(x, op, ...) Sequence(Read4(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(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), \ @@ -154,18 +148,18 @@ ProcessorStorage::ProcessorStorage() { RMWI(a_, op) #define IX_READ_OP_GROUP(op) \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - StdInstr(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}) + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(Read4Pre(INDEX_ADDR(), temp8_), {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)); @@ -203,7 +197,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 } @@ -213,14 +208,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 } @@ -230,7 +225,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_), @@ -245,8 +240,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 @@ -256,58 +251,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(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write3(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(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write3(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(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDI}), + /* 0xa1 CPI */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}), + /* 0xa2 INI */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INI}), + /* 0xa3 OTI */ Sequence(InternalOperation(2), Read3(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(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDD}), + /* 0xa9 CPD */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}), + /* 0xaa IND */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::IND}), + /* 0xab OTD */ Sequence(InternalOperation(2), Read3(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(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDIR}, InternalOperation(10)), + /* 0xb1 CPIR */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)), + /* 0xb2 INIR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)), + /* 0xb3 OTIR */ Sequence(InternalOperation(2), Read3(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(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDDR}, InternalOperation(10)), + /* 0xb9 CPDR */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)), + /* 0xba INDR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)), + /* 0xbb OTDR */ Sequence(InternalOperation(2), Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)), NOP, NOP, NOP, NOP, NOP_ROW(), /* 0xc0 */ NOP_ROW(), /* 0xd0 */ @@ -347,77 +342,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}, Write3(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}, Read3(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}, Write3(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}, Read3(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}, Write3(temp16_, a_)), + /* 0x33 INC SP */ Sequence(InternalOperation(4), {MicroOp::Increment16, &sp_.full}), + /* 0x34 INC (HL) */ Sequence(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Increment8, &temp8_}, Write3(INDEX_ADDR(), temp8_)), + /* 0x35 DEC (HL) */ Sequence(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Decrement8, &temp8_}, Write3(INDEX_ADDR(), temp8_)), + /* 0x36 LD (HL), n */ Sequence(ReadInc(pc_, temp8_), Write3(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_), Read3(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), @@ -437,14 +432,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(), Write3(INDEX_ADDR(), bc_.halves.high)), + /* 0x71 LD (HL), C */ Sequence(INDEX(), Write3(INDEX_ADDR(), bc_.halves.low)), + /* 0x72 LD (HL), D */ Sequence(INDEX(), Write3(INDEX_ADDR(), de_.halves.high)), + /* 0x73 LD (HL), E */ Sequence(INDEX(), Write3(INDEX_ADDR(), de_.halves.low)), + /* 0x74 LD (HL), H */ Sequence(INDEX(), Write3(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register; + /* 0x75 LD (HL), L */ Sequence(INDEX(), Write3(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L. + /* 0x76 HALT */ Sequence({MicroOp::HALT}), + /* 0x77 LD (HL), A */ Sequence(INDEX(), Write3(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_), @@ -473,45 +468,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), Read4Inc(pc_, memptr_.halves.high), 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(Pop7(memptr_), Push8(index), {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(), }; @@ -519,7 +514,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(), Read5Inc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_)) }; std::memcpy(&base_program_table[0x36], ©_table[0], sizeof(copy_table[0])); } @@ -533,6 +528,8 @@ void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, in BusOp(ReadOpcodeStart()), BusOp(ReadOpcodeWait(true)), BusOp(ReadOpcodeEnd()), + { MicroOp::IncrementR }, + BusOp(Refresh()), { MicroOp::DecodeOperation } }; const MicroOp short_fetch_decode_execute[] = { @@ -540,6 +537,8 @@ void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, in BusOp(ReadOpcodeWait(true)), BusOp(ReadOpcodeWait(false)), BusOp(ReadOpcodeEnd()), + { MicroOp::IncrementR }, + BusOp(Refresh()), { MicroOp::DecodeOperation }, }; copy_program((length == 4) ? normal_fetch_decode_execute : short_fetch_decode_execute, target.fetch_decode_execute); diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index c682a259c..e107ef312 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, From e82367def3cc2a83219066e71ffcb6080f239efb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 11 Apr 2021 23:01:00 -0400 Subject: [PATCH 27/41] Switches to test-conformant behaviour for (IX/IY+n) opcode fetches. --- .../Mac/Clock SignalTests/Z80ContentionTests.mm | 2 +- Processors/Z80/Implementation/Z80Storage.cpp | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 13800c8e8..5d33fab1c 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -57,7 +57,7 @@ struct CapturingZ80: public CPU::Z80::BusHandler { HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { // Log the activity. - const uint8_t* const bus_state = cycle.bus_state(); + const uint8_t *const bus_state = cycle.bus_state(); for(int c = 0; c < cycle.length.as(); c++) { bus_records_.emplace_back(); diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index e2b23d329..22ec3d64b 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -524,6 +524,7 @@ 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)), @@ -532,15 +533,18 @@ void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, in 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(true)), - BusOp(ReadOpcodeWait(false)), - BusOp(ReadOpcodeEnd()), - { MicroOp::IncrementR }, - BusOp(Refresh()), + BusOp(ReadStart(pc_, operation_)), + BusOp(ReadWait(2, pc_, operation_, true)), + 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(); } From 9347fe5f44051206a9dafbc39675a2506774e5f7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Apr 2021 17:11:58 -0400 Subject: [PATCH 28/41] Advances to next failing test: `LD (ii+n), n`. --- .../Clock SignalTests/Z80ContentionTests.mm | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 5d33fab1c..a12b18195 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -602,4 +602,79 @@ struct ContentionCheck { } } +- (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]; + } +} + @end From 947de2d54a5c601e45591cce658799370210c360 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Apr 2021 17:17:08 -0400 Subject: [PATCH 29/41] Switches five-cycle read to a post hoc pause. --- Processors/Z80/Implementation/Z80Storage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index 22ec3d64b..1a11b6712 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -57,7 +57,7 @@ ProcessorStorage::ProcessorStorage() { #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(ReadEnd(addr, val)), InternalOperation(2) -#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 Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(4) #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)) From 36c8821c4c909651d4649686d329f04babf4c1a7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Apr 2021 17:29:03 -0400 Subject: [PATCH 30/41] Reaches the halfway point in tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index a12b18195..0948f77bc 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -677,4 +677,109 @@ struct ContentionCheck { } } +- (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]; + } +} + @end From b42780173a365839c33d3d3f6f2033bd79c73a89 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Apr 2021 20:54:10 -0400 Subject: [PATCH 31/41] Establishes that there really is no Read4 and Read4Pre distinction. Will finish these unit tests, then clean up. --- .../Clock SignalTests/Z80ContentionTests.mm | 128 ++++++++++++++++++ Processors/Z80/Implementation/Z80Storage.cpp | 2 +- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 0948f77bc..5408b1808 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -782,4 +782,132 @@ struct ContentionCheck { } } +- (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]; + } +} @end diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index 1a11b6712..92a52ca01 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -55,7 +55,7 @@ ProcessorStorage::ProcessorStorage() { // Read4Pre is a four-cycle read that has to do something after reading: 1.5 cycles, then check the wait line, then 1.5 cycles, then a 1-cycle internal operation; // 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 Read4(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(2) #define Read4Pre(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(2) #define Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(4) From 06f1e641778ea5ba155c31d40e392551bc043d59 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Apr 2021 21:41:20 -0400 Subject: [PATCH 32/41] Advances to IO. --- .../Clock SignalTests/Z80ContentionTests.mm | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 5408b1808..34081327b 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -16,6 +16,7 @@ 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 { @@ -29,6 +30,13 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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); @@ -41,6 +49,9 @@ struct CapturingZ80: public CPU::Z80::BusHandler { // 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 run_for(int cycles) { @@ -910,4 +921,214 @@ struct ContentionCheck { } 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]; + } +} + @end From 8a3bfb86729f653f608f82e0b8a480e293e873f4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 17:55:51 -0400 Subject: [PATCH 33/41] Adds an IN/OUT test. --- .../Clock SignalTests/Z80ContentionTests.mm | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 34081327b..241783eaa 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -131,6 +131,7 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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 { @@ -142,6 +143,7 @@ struct ContentionCheck { 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; @@ -164,26 +166,32 @@ struct ContentionCheck { 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].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"]; } @@ -199,27 +207,35 @@ struct ContentionCheck { 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; - const bool is_leading_edge = !bus_records[c].mreq && bus_records[c+1].mreq && !bus_records[c].refresh; + // 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_leading_edge // i.e. beginning of a new contention. + (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"]; } @@ -1131,4 +1147,25 @@ struct ContentionCheck { } } +- (void)testINOUTA { + 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]; + } +} + @end From 2e70b5eb9f1723a261060765247f09a97a592b9a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 19:45:29 -0400 Subject: [PATCH 34/41] Advances to EX (SP), HL, leaving only [LD/CP/IN/OT][I/D]{R}. --- .../Clock SignalTests/Z80ContentionTests.mm | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 241783eaa..85b1ed3ed 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -1147,7 +1147,7 @@ struct ContentionCheck { } } -- (void)testINOUTA { +- (void)testINOUTn { for(const auto &sequence : std::vector>{ {0xdb, 0xef}, // IN A, (n) {0xd3, 0xef}, // OUT (n), A @@ -1168,4 +1168,57 @@ struct ContentionCheck { } } +- (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]; + }} + @end From 869567fdd982f7b3465d04f0c66ae28b858f7b22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 19:45:48 -0400 Subject: [PATCH 35/41] Corrects `EX (SP), HL` breakdown. --- Processors/Z80/Implementation/Z80Storage.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index 92a52ca01..a0519a279 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -94,9 +94,6 @@ ProcessorStorage::ProcessorStorage() { #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} - /* The following are actual instructions */ #define NOP { {MicroOp::MoveToNextProgram} } @@ -489,7 +486,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 0xde SBC A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}), /* 0xdf RST 18h */ RST(), /* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ Sequence(Pop(index)), - /* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */Sequence(Pop7(memptr_), Push8(index), {MicroOp::Move16, &memptr_.full, &index.full}), + /* 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(), From 5998f3b35b19906744f44815f583db13ac5fa4fd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 20:00:18 -0400 Subject: [PATCH 36/41] Corrects LD[I/D/IR/DR] timing. Macro cleanup to come. --- Processors/Z80/Implementation/Z80Storage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index a0519a279..b290110c3 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -60,7 +60,7 @@ ProcessorStorage::ProcessorStorage() { #define Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(4) #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 Write5(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val)), InternalOperation(4) #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)) From 3eec210b304e18ad83707cfab7c31dab7f0ddb15 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 20:00:29 -0400 Subject: [PATCH 37/41] Adds LDI/LDD/LDIR/LDDR tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 85b1ed3ed..15ee17392 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -54,6 +54,10 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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); @@ -1219,6 +1223,72 @@ struct ContentionCheck { {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]; + } +} @end From 0d61902b107be7ea0bbc0e209b49a42c59063763 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 20:03:11 -0400 Subject: [PATCH 38/41] Adds CP[I/D/IR/DR] tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 15ee17392..f381b2c20 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -1291,4 +1291,62 @@ struct ContentionCheck { } } +- (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 From 68a04f4e6a220b426e97827fce00e5975d680d0d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 22:00:24 -0400 Subject: [PATCH 39/41] Adds IN/OUT I/D [R] to complete tests. --- .../Clock SignalTests/Z80ContentionTests.mm | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index f381b2c20..2c03cb83e 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -58,6 +58,10 @@ struct CapturingZ80: public CPU::Z80::BusHandler { 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); @@ -1348,5 +1352,121 @@ struct ContentionCheck { } } +- (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 From deb5d69ac7169b8323390fa087cfa3a5432e15a9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 22:11:28 -0400 Subject: [PATCH 40/41] Consolidates macros. --- Processors/Z80/Implementation/Z80Storage.cpp | 136 +++++++++---------- 1 file changed, 61 insertions(+), 75 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index b290110c3..c4ef81184 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -23,11 +23,11 @@ ProcessorStorage::ProcessorStorage() { #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,21 +47,9 @@ ProcessorStorage::ProcessorStorage() { #define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op} // Compound bus operations, as micro-ops - #define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}} - -// 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 1.5 cycles, then a 1-cycle internal operation; -// 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, true)), BusOp(ReadEnd(addr, val)), InternalOperation(2) -#define Read4Pre(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(2) -#define Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val)), InternalOperation(4) - -#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(2, addr, val, true)), BusOp(WriteEnd(addr, val)), InternalOperation(4) - +#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)) @@ -77,28 +65,26 @@ 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 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 { {MicroOp::MoveToNextProgram} } #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()}, Read4Inc(pc_, memptr_.halves.high), Push(pc_), {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}) @@ -107,25 +93,25 @@ ProcessorStorage::ProcessorStorage() { #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), \ - Sequence(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) \ 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(), Read3(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ Sequence({MicroOp::op, &a_}) #define READ_OP_GROUP_D(op) \ 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(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ + Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \ Sequence({MicroOp::op, &a_}) -#define RMW(x, op, ...) Sequence(INDEX(), Read4Pre(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x)) -#define RMWI(x, op, ...) Sequence(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) \ Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \ @@ -145,14 +131,14 @@ ProcessorStorage::ProcessorStorage() { RMWI(a_, op) #define IX_READ_OP_GROUP(op) \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ - Sequence(Read4Pre(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) 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}) @@ -266,11 +252,11 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) { /* 0x60 IN H, (C); 0x61 OUT (C), H */ IN_OUT(hl_.halves.high), /* 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(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write3(hl_, temp8_)), + /* 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) */ 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(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write3(hl_, temp8_)), + /* 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}), @@ -281,25 +267,25 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) { /* 0x7e IM 2 */ Sequence({MicroOp::IM}), /* 0x7f XX */ NOP, NOP_ROW(), /* 0x80 ... 0x8f */ NOP_ROW(), /* 0x90 ... 0x9f */ - /* 0xa0 LDI */ Sequence(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDI}), - /* 0xa1 CPI */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}), - /* 0xa2 INI */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INI}), - /* 0xa3 OTI */ Sequence(InternalOperation(2), 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 */ Sequence(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDD}), - /* 0xa9 CPD */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}), - /* 0xaa IND */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::IND}), - /* 0xab OTD */ Sequence(InternalOperation(2), 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 */ Sequence(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDIR}, InternalOperation(10)), - /* 0xb1 CPIR */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)), - /* 0xb2 INIR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)), - /* 0xb3 OTIR */ Sequence(InternalOperation(2), 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 */ Sequence(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDDR}, InternalOperation(10)), - /* 0xb9 CPDR */ Sequence(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)), - /* 0xba INDR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)), - /* 0xbb OTDR */ Sequence(InternalOperation(2), 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 */ @@ -351,14 +337,14 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 InstructionTable base_program_table = { /* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ Sequence(Read16Inc(pc_, bc_)), - /* 0x02 LD (BC), A */ Sequence({MicroOp::SetAddrAMemptr, &bc_.full}, Write3(bc_, a_)), + /* 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 */ 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}, Read3(memptr_, a_), Inc16(memptr_)), + /* 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), @@ -366,7 +352,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 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}, Write3(de_, a_)), + /* 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), @@ -374,7 +360,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 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) */ Sequence({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), @@ -395,15 +381,15 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 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}, Write3(temp16_, a_)), + /* 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(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Increment8, &temp8_}, Write3(INDEX_ADDR(), temp8_)), - /* 0x35 DEC (HL) */ Sequence(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Decrement8, &temp8_}, Write3(INDEX_ADDR(), temp8_)), - /* 0x36 LD (HL), n */ Sequence(ReadInc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_)), + /* 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) */ Sequence(Read16Inc(pc_, memptr_), Read3(memptr_, a_), Inc16(memptr_)), + /* 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 */ @@ -429,14 +415,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 */ Sequence(INDEX(), Write3(INDEX_ADDR(), bc_.halves.high)), - /* 0x71 LD (HL), C */ Sequence(INDEX(), Write3(INDEX_ADDR(), bc_.halves.low)), - /* 0x72 LD (HL), D */ Sequence(INDEX(), Write3(INDEX_ADDR(), de_.halves.high)), - /* 0x73 LD (HL), E */ Sequence(INDEX(), Write3(INDEX_ADDR(), de_.halves.low)), - /* 0x74 LD (HL), H */ Sequence(INDEX(), Write3(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register; - /* 0x75 LD (HL), L */ Sequence(INDEX(), Write3(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L. + /* 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(), Write3(INDEX_ADDR(), a_)), + /* 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,7 +458,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1 /* 0xc7 RST 00h */ RST(), /* 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), Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}), + /* 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 */ Sequence(Pop(de_)), @@ -511,7 +497,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 = { - Sequence(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])); } @@ -536,7 +522,7 @@ void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, in /// taken a punt on it not incrementing R. const MicroOp short_fetch_decode_execute[] = { BusOp(ReadStart(pc_, operation_)), - BusOp(ReadWait(2, pc_, operation_, true)), + BusOp(ReadWait(pc_, operation_)), BusOp(ReadEnd(pc_, operation_)), InternalOperation(4), { MicroOp::DecodeOperation }, From 7017324d60a5dbdc95a65468bdcbef7babf1750c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Apr 2021 22:17:30 -0400 Subject: [PATCH 41/41] `r_step` is obsolete now that I know that [DD/FD]CB don't have a refresh cycle. --- Processors/Z80/Implementation/Z80Implementation.hpp | 2 +- Processors/Z80/Implementation/Z80Storage.cpp | 3 --- Processors/Z80/Implementation/Z80Storage.hpp | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 12f637ca5..490e5f50f 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -94,7 +94,7 @@ template < class T, break; 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_); diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index c4ef81184..8f2404ed8 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -153,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; diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index e107ef312..613e9334a 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -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; };