diff --git a/InstructionSets/M68k/Instruction.hpp b/InstructionSets/M68k/Instruction.hpp index 314729f8d..75c55ed29 100644 --- a/InstructionSets/M68k/Instruction.hpp +++ b/InstructionSets/M68k/Instruction.hpp @@ -250,6 +250,8 @@ enum class AddressingMode: uint8_t { /// .q; value is embedded in the opcode. Quick = 0b01'110, }; +/// Guaranteed to be 1+[largest value used by AddressingMode]. +static constexpr int AddressingModeCount = 0b10'110; /*! A preinstruction is as much of an instruction as can be decoded with diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 47267a550..add620202 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -190,6 +190,7 @@ template class ConcreteMachin void run_for(const Cycles cycles) final { mc68000_.run_for(cycles); + flush(); } using Microcycle = CPU::MC68000Mk2::Microcycle; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 47c6a8f14..5ced38bd7 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -278,6 +278,7 @@ 4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; }; 4B5B37312777C7FC0047F238 /* IPF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B372F2777C7FC0047F238 /* IPF.cpp */; }; 4B5B37322777C7FC0047F238 /* IPF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B372F2777C7FC0047F238 /* IPF.cpp */; }; + 4B5D497C28513F870076E2F9 /* IPF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5B372F2777C7FC0047F238 /* IPF.cpp */; }; 4B5D5C9725F56FC7001B4623 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; }; 4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; }; 4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; }; @@ -641,6 +642,7 @@ 4B9F11CC22729B3600701480 /* OPCLOGR2.BIN in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */; }; 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; }; 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; + 4BA6B6AE284EDAC100A3B7A8 /* 68000OldVsNew.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA6B6AD284EDAC000A3B7A8 /* 68000OldVsNew.mm */; }; 4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */; }; 4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; }; @@ -1683,6 +1685,7 @@ 4BA3AE44283317CB00328FED /* RegisterSet.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSet.hpp; sourceTree = ""; }; 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = ""; }; 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = ""; }; + 4BA6B6AD284EDAC000A3B7A8 /* 68000OldVsNew.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000OldVsNew.mm; sourceTree = ""; }; 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MasterSystemVDPTests.mm; sourceTree = ""; }; 4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = ""; }; 4BAA167B21582B1D008A3276 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; @@ -4235,6 +4238,7 @@ 4B75F978280D7C5100121055 /* 68000DecoderTests.mm */, 4B7C79FF282C3BCA002D6C0B /* 68000flamewingTests.mm */, 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */, + 4BA6B6AD284EDAC000A3B7A8 /* 68000OldVsNew.mm */, 4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */, 4BD388872239E198002D14B5 /* 68000Tests.mm */, 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */, @@ -5975,6 +5979,7 @@ 4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */, 4B778F0A23A5EC150000D260 /* TapePRG.cpp in Sources */, 4B778F0823A5EC150000D260 /* CSW.cpp in Sources */, + 4BA6B6AE284EDAC100A3B7A8 /* 68000OldVsNew.mm in Sources */, 4B778F5323A5F23F0000D260 /* SerialBus.cpp in Sources */, 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, 4B7752BA28217F160073E2C5 /* Bitplanes.cpp in Sources */, @@ -6118,6 +6123,7 @@ 4B7752C328217F720073E2C5 /* Z80.cpp in Sources */, 4B778F1A23A5ED320000D260 /* Video.cpp in Sources */, 4B778F3B23A5F1650000D260 /* KeyboardMachine.cpp in Sources */, + 4B5D497C28513F870076E2F9 /* IPF.cpp in Sources */, 4B778F2E23A5F09E0000D260 /* IRQDelegatePortHandler.cpp in Sources */, 4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */, 4B778F0D23A5EC150000D260 /* ZX80O81P.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm new file mode 100644 index 000000000..3b1efcdef --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm @@ -0,0 +1,436 @@ +// +// 68000ArithmeticTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 28/06/2019. +// +// Largely ported from the tests of the Portable 68k Emulator. +// + +#import + +#include "68000.hpp" +#include "68000Mk2.hpp" + +#include +#include +#include + +namespace { + +struct RandomStore { + using CollectionT = std::unordered_map>; + CollectionT values; + + void flag(uint32_t address, uint8_t participant) { + values[address].first |= participant; + } + + bool has(uint32_t address, uint8_t participant) { + auto entry = values.find(address); + if(entry == values.end()) return false; + return entry->second.first & participant; + } + + uint8_t value(uint32_t address, uint8_t participant) { + auto entry = values.find(address); + if(entry != values.end()) { + entry->second.first |= participant; + return entry->second.second; + } + + const uint8_t value = uint8_t(rand() >> 8); + values[address] = std::make_pair(participant, value); + return value; + } + + void clear() { + values.clear(); + } + +}; + +struct Transaction { + HalfCycles timestamp; + uint8_t function_code = 0; + uint32_t address = 0; + uint16_t value = 0; + bool address_strobe = false; + bool read = false; + int data_strobes = 0; + + bool operator !=(const Transaction &rhs) const { + if(timestamp != rhs.timestamp) return true; +// if(function_code != rhs.function_code) return true; + if(address != rhs.address) return true; + if(value != rhs.value) return true; + if(address_strobe != rhs.address_strobe) return true; + if(data_strobes != rhs.data_strobes) return true; + return false; + } + + void print() const { + printf("%d: %d%d%d %c %c%c @ %06x %s %04x\n", + timestamp.as(), + (function_code >> 2) & 1, + (function_code >> 1) & 1, + (function_code >> 0) & 1, + address_strobe ? 'a' : '-', + (data_strobes & 1) ? 'b' : '-', + (data_strobes & 2) ? 'w' : '-', + address, + read ? "->" : "<-", + value); + } +}; + +struct HarmlessStopException {}; + +struct BusHandler { + BusHandler(RandomStore &_store, uint8_t _participant) : store(_store), participant(_participant) {} + + void will_perform(uint32_t, uint16_t) { + --instructions; + if(instructions < 0) { + throw HarmlessStopException{}; + } + } + + template HalfCycles perform_bus_operation(const Microcycle &cycle, bool is_supervisor) { + Transaction transaction; + + // Fill all of the transaction record except the data field; will do that after + // any potential read. + if(cycle.operation & Microcycle::InterruptAcknowledge) { + transaction.function_code = 0b111; + } else { + transaction.function_code = is_supervisor ? 0x4 : 0x0; + transaction.function_code |= (cycle.operation & Microcycle::IsData) ? 0x1 : 0x2; + } + transaction.address_strobe = cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress); + transaction.data_strobes = cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord); + if(cycle.address) transaction.address = *cycle.address & 0xffff'ff; + transaction.timestamp = time; + transaction.read = cycle.operation & Microcycle::Read; + + time += cycle.length; + + // Do the operation... + const uint32_t address = cycle.address ? (*cycle.address & 0xffff'ff) : 0; + switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: break; + + case Microcycle::SelectWord | Microcycle::Read: + if(!store.has(address, participant)) { + ram[address] = store.value(address, participant); + } + if(!store.has(address+1, participant)) { + ram[address+1] = store.value(address+1, participant); + } + + cycle.set_value16((ram[address] << 8) | ram[address + 1]); + break; + case Microcycle::SelectByte | Microcycle::Read: + if(!store.has(address, participant)) { + ram[address] = store.value(address, participant); + } + + if(address & 1) { + cycle.set_value8_low(ram[address]); + } else { + cycle.set_value8_high(ram[address]); + } + break; + case Microcycle::SelectWord: + ram[address] = cycle.value8_high(); + ram[address+1] = cycle.value8_low(); + store.flag(address, participant); + store.flag(address+1, participant); + break; + case Microcycle::SelectByte: + ram[address] = (address & 1) ? cycle.value8_low() : cycle.value8_high(); + store.flag(address, participant); + break; + } + + + // Add the data value if relevant. + if(transaction.data_strobes) { + transaction.value = cycle.value16(); + } + + // Push back only if interesting. + if(transaction.address_strobe || transaction.data_strobes || transaction.function_code == 7) { + if(transaction_delay) { + --transaction_delay; + + // Start counting time only from the first recorded transaction. + if(!transaction_delay) { + time = HalfCycles(0); + } + } else { + transactions.push_back(transaction); + } + } + + return HalfCycles(0); + } + + void flush() {} + + int transaction_delay; + int instructions; + + HalfCycles time; + std::vector transactions; + std::array ram; + + void set_default_vectors() { + // Establish that all exception vectors point to 1024-byte blocks of memory. + for(int c = 0; c < 256; c++) { + const uint32_t target = (c + 2) << 10; + const uint32_t address = c << 2; + ram[address + 0] = uint8_t(target >> 24); + ram[address + 1] = uint8_t(target >> 16); + ram[address + 2] = uint8_t(target >> 8); + ram[address + 3] = uint8_t(target >> 0); + + store.flag(address+0, participant); + store.flag(address+1, participant); + store.flag(address+2, participant); + store.flag(address+3, participant); + } + } + + RandomStore &store; + const uint8_t participant; +}; + +using OldProcessor = CPU::MC68000::Processor; +using NewProcessor = CPU::MC68000Mk2::Processor; + +template struct Tester { + Tester(RandomStore &store, uint8_t participant) : bus_handler(store, participant), processor(bus_handler) {} + + void run_instructions(int instructions) { + bus_handler.instructions = instructions; + + try { + processor.run_for(HalfCycles(5000)); // Arbitrary, but will definitely exceed any one instruction (by quite a distance). + } catch (const HarmlessStopException &) {} + } + + void reset_with_opcode(uint16_t opcode) { + bus_handler.transactions.clear(); + bus_handler.set_default_vectors(); + + const uint32_t address = 3 << 10; + bus_handler.ram[address + 0] = uint8_t(opcode >> 8); + bus_handler.ram[address + 1] = uint8_t(opcode >> 0); + bus_handler.store.flag(address, bus_handler.participant); + bus_handler.store.flag(address+1, bus_handler.participant); + + bus_handler.transaction_delay = 12; // i.e. ignore everything from the RESET sequence. + bus_handler.time = HalfCycles(0); + + processor.reset(); + } + + BusHandler bus_handler; + M68000 processor; +}; + +} + +@interface M68000OldVsNewTests : XCTestCase +@end + +@implementation M68000OldVsNewTests + +- (void)testOldVsNew { + RandomStore random_store; + auto oldTester = std::make_unique>(random_store, 0x01); + auto newTester = std::make_unique>(random_store, 0x02); + InstructionSet::M68k::Predecoder decoder; + + // Use a fixed seed to guarantee continuity across repeated runs. + srand(68000); + + std::set ignore_list = { + // + // Operations that do the wrong thing on the old 68000: + // + InstructionSet::M68k::Operation::ABCD, // Old implementation doesn't match flamewing tests, sometimes produces incorrect results. + InstructionSet::M68k::Operation::SBCD, // Old implementation doesn't match flamewing tests, sometimes produces incorrect results. + InstructionSet::M68k::Operation::JSR, // Old implementation ends up skipping stack space if the destination throws an address error. + InstructionSet::M68k::Operation::MOVEtoSR, // Old implementation doesn't repeat a PC fetch. + InstructionSet::M68k::Operation::MOVEtoCCR, // Old implementation doesn't repeat a PC fetch. + + // + // Operations with definite timing deficiencies versus Yacht.txt on the old 68000: + // + InstructionSet::M68k::Operation::CMPAl, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::CLRb, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::CLRw, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::NEGXb, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::NEGXw, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::NEGb, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::NEGw, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::NOTb, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::NOTw, // Old implementation omits an idle cycle before -(An) + InstructionSet::M68k::Operation::TRAP, // Old implementation relocates the idle state near the end to the beginning. + InstructionSet::M68k::Operation::TRAPV, // Old implementation relocates the idle state near the end to the beginning. + InstructionSet::M68k::Operation::CHK, // Old implementation pauses four cycles too long. + InstructionSet::M68k::Operation::TAS, // Old implementation just doesn't match published cycle counts. + + // + // Operations with timing discrepancies between the two 68000 implementations + // that I think are _more_ accurate now, but possibly still need work: + // + InstructionSet::M68k::Operation::MULU, + InstructionSet::M68k::Operation::MULS, + InstructionSet::M68k::Operation::DIVU, + InstructionSet::M68k::Operation::DIVS, + }; + + int testsRun = 0; + std::set failing_operations; + for(int c = 0; c < 65536; c++) { +// printf("%04x\n", c); + + // Test only defined opcodes that aren't STOP (which will never teminate). + const auto instruction = decoder.decode(uint16_t(c)); + if( + instruction.operation == InstructionSet::M68k::Operation::Undefined || + instruction.operation == InstructionSet::M68k::Operation::STOP + ) { + continue; + } + + // If this operation is known to diverge, ignore it. It's dealt with. + if(ignore_list.find(instruction.operation) != ignore_list.end()) { + continue; + } + + // Test each 1000 times. + for(int test = 0; test < 1000; test++) { + ++testsRun; + + // Establish with certainty the initial memory state. + random_store.clear(); + newTester->reset_with_opcode(c); + oldTester->reset_with_opcode(c); + + // Generate a random initial register state. + auto oldState = oldTester->processor.get_state(); + auto newState = newTester->processor.get_state(); + + for(int c = 0; c < 8; c++) { + oldState.data[c] = newState.registers.data[c] = rand() ^ (rand() << 1); + if(c != 7) oldState.address[c] = newState.registers.address[c] = rand() << 1; + } + // Fully to paper over the two 68000s' different ways of doing a faked + // reset, pick a random status such that: + // + // (i) supervisor mode is active; + // (ii) trace is inactive; and + // (iii) interrupt level is 7. + oldState.status = newState.registers.status = (rand() | (1 << 13) | (7 << 8)) & ~(1 << 15); + oldState.user_stack_pointer = newState.registers.user_stack_pointer = rand() << 1; + oldState.supervisor_stack_pointer = newState.registers.supervisor_stack_pointer = 0x800; + + newTester->processor.set_state(newState); + oldTester->processor.set_state(oldState); + + // Run a single instruction. + newTester->run_instructions(1); + oldTester->run_instructions(1); + + // Grab final states. + oldState = oldTester->processor.get_state(); + newState = newTester->processor.get_state(); + + // Compare bus activity only if it doesn't look like an address + // error occurred. Don't check those as the old 68000 appears to be wrong + // most of the time about function codes, and that bleeds into the stacked data. + // + // Net effect will be 50% fewer transaction comparisons for instructions that + // can trigger an address error. + const auto &oldTransactions = oldTester->bus_handler.transactions; + const auto &newTransactions = newTester->bus_handler.transactions; + if(oldState.program_counter != 0x1404 || newState.registers.program_counter != 0x1404) { + auto newIt = newTransactions.begin(); + auto oldIt = oldTransactions.begin(); + while(newIt != newTransactions.end() && oldIt != oldTransactions.end()) { + if(*newIt != *oldIt) { + printf("Mismatch in %s, test %d:\n", instruction.to_string().c_str(), test); + + auto repeatIt = newTransactions.begin(); + while(repeatIt != newIt) { + repeatIt->print(); + ++repeatIt; + } + printf("---\n"); + while(newIt != newTransactions.end()) { + printf("n: "); newIt->print(); + ++newIt; + } + printf("\n"); + while(oldIt != oldTransactions.end()) { + printf("o: "); oldIt->print(); + ++oldIt; + } + printf("\n"); + + failing_operations.insert(instruction.operation); + break; + } + + ++newIt; + ++oldIt; + } + } + + // Compare registers. + bool mismatch = false; + for(int c = 0; c < 8; c++) { + mismatch |= oldState.data[c] != newState.registers.data[c]; + if(c != 7) mismatch |= oldState.address[c] != newState.registers.address[c]; + } + mismatch |= oldState.status != newState.registers.status; + mismatch |= oldState.program_counter != newState.registers.program_counter; + mismatch |= oldState.user_stack_pointer != newState.registers.user_stack_pointer; + mismatch |= oldState.supervisor_stack_pointer != newState.registers.supervisor_stack_pointer; + + if(mismatch) { + failing_operations.insert(instruction.operation); + printf("Registers don't match after %s, test %d\n", instruction.to_string().c_str(), test); + for(const auto &transaction: newTransactions) { + printf("n: "); transaction.print(); + } + printf("\n"); + for(const auto &transaction: oldTransactions) { + printf("o: "); transaction.print(); + } + printf("\n"); + + // TODO: more detail here! + } + } + } + + printf("%d tests run\n", testsRun); + if(failing_operations.empty()) { + printf("No failures\n"); + } else { + printf("\nAll failing operations:\n"); + for(const auto operation: failing_operations) { + printf("%d,\n", int(operation)); + } + } + + // Mark the test as passed or failed. + XCTAssert(failing_operations.empty()); +} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/68000Tests.mm b/OSBindings/Mac/Clock SignalTests/68000Tests.mm index 8cebc0385..b1289b5aa 100644 --- a/OSBindings/Mac/Clock SignalTests/68000Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/68000Tests.mm @@ -189,6 +189,9 @@ // PC. XCTAssertEqual(stack_frame[5], 0x0000); XCTAssertEqual(stack_frame[6], 0x1004); + + // Check that A7 ended up in the proper location. + XCTAssertEqual(_machine->get_processor_state().registers.stack_pointer(), 0x1f8); } - (void)testShiftDuration { diff --git a/Processors/68000/68000.hpp b/Processors/68000/68000.hpp index 068cee4e3..94f11c95c 100644 --- a/Processors/68000/68000.hpp +++ b/Processors/68000/68000.hpp @@ -462,6 +462,8 @@ template cla return e_clock_phase_; } + void reset(); + private: T &bus_handler_; }; diff --git a/Processors/68000/Implementation/68000Implementation.hpp b/Processors/68000/Implementation/68000Implementation.hpp index 812d2a0ab..18441397e 100644 --- a/Processors/68000/Implementation/68000Implementation.hpp +++ b/Processors/68000/Implementation/68000Implementation.hpp @@ -2237,6 +2237,15 @@ void ProcessorStorage::set_status(uint16_t status) { apply_status(status); } +template void Processor::reset() { + execution_state_ = ExecutionState::Executing; + active_step_ = reset_bus_steps_; + effective_address_[0] = 0; + is_supervisor_ = 1; + interrupt_level_ = 7; + half_cycles_left_to_run_ = HalfCycles(0); +} + #undef status #undef apply_status #undef apply_ccr diff --git a/Processors/68000Mk2/68000Mk2.hpp b/Processors/68000Mk2/68000Mk2.hpp index f58777494..5defa8eac 100644 --- a/Processors/68000Mk2/68000Mk2.hpp +++ b/Processors/68000Mk2/68000Mk2.hpp @@ -441,6 +441,8 @@ class Processor: private ProcessorBase { return e_clock_phase_; } + void reset(); + private: BusHandler &bus_handler_; }; diff --git a/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp b/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp index faed7b03f..1acfef412 100644 --- a/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp +++ b/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp @@ -17,6 +17,9 @@ namespace CPU { namespace MC68000Mk2 { +#define AddressingDispatch(x) \ + x, x##__end = x + InstructionSet::M68k::AddressingModeCount + /// States for the state machine which are named by /// me for their purpose rather than automatically by file position. /// These are negative to avoid ambiguity with the other group. @@ -25,11 +28,6 @@ enum ExecutionState: int { Decode, WaitForDTACK, - /// Perform the proper sequence to fetch a byte or word operand. - FetchOperand_bw, - /// Perform the proper sequence to fetch a long-word operand. - FetchOperand_l, - StoreOperand, StoreOperand_bw, StoreOperand_l, @@ -64,42 +62,45 @@ enum ExecutionState: int { // Further consideration may be necessary. Especially once this is // up on its feet and profiling becomes an option. - FetchAddressRegisterIndirect_bw, - FetchAddressRegisterIndirectWithPostincrement_bw, - FetchAddressRegisterIndirectWithPredecrement_bw, - FetchAddressRegisterIndirectWithDisplacement_bw, - FetchAddressRegisterIndirectWithIndex8bitDisplacement_bw, - FetchProgramCounterIndirectWithDisplacement_bw, - FetchProgramCounterIndirectWithIndex8bitDisplacement_bw, - FetchAbsoluteShort_bw, - FetchAbsoluteLong_bw, - FetchImmediateData_bw, + /// Perform the proper sequence to fetch a byte or word operand. + /// i.e. + /// + /// Dn/An/Q - (An) nr + /// (An)+ nr -(An) n nr + /// (d16, An) np nr (d8, An, Xn) n np nr + /// (d16, PC) np nr (d8, PC, Xn) n np nr + /// (xxx).w np nr (xxx).l np np nr + /// # np + AddressingDispatch(FetchOperand_bw), - FetchAddressRegisterIndirect_l, - FetchAddressRegisterIndirectWithPostincrement_l, - FetchAddressRegisterIndirectWithPredecrement_l, - FetchAddressRegisterIndirectWithDisplacement_l, - FetchAddressRegisterIndirectWithIndex8bitDisplacement_l, - FetchProgramCounterIndirectWithDisplacement_l, - FetchProgramCounterIndirectWithIndex8bitDisplacement_l, - FetchAbsoluteShort_l, - FetchAbsoluteLong_l, - FetchImmediateData_l, + /// Perform the proper sequence to fetch a long-word operand. + /// i.e. + /// + /// Dn/An/Q - (An) nR nr + /// (An)+ nR nr -(An) n nR nr + /// (d16, An) np nR nr (d8, An, Xn) n np nR nr + /// (d16, PC) np nR nr (d8, PC, Xn) n np nR nr + /// (xxx).w np nR nr (xxx).l np np nR nr + /// # np np + AddressingDispatch(FetchOperand_l), - CalcEffectiveAddress, // - - CalcAddressRegisterIndirect, // - - CalcAddressRegisterIndirectWithPostincrement, // - - CalcAddressRegisterIndirectWithPredecrement, // - - CalcAddressRegisterIndirectWithDisplacement, // np - CalcAddressRegisterIndirectWithIndex8bitDisplacement, // np n - CalcProgramCounterIndirectWithDisplacement, // np - CalcProgramCounterIndirectWithIndex8bitDisplacement, // np n - CalcAbsoluteShort, // np - CalcAbsoluteLong, // np np + /// Perform the sequence to calculate an effective address, but don't fetch from it. + /// There's a lack of uniformity in the bus programs used by the 68000 for relevant + /// instructions; this entry point uses: + /// + /// Dn/An - (An) - + /// (An)+ - -(An) - + /// (d16, An) np (d8, An, Xn) np n + /// (d16, PC) np (d8, PC, Xn) np n + /// (xxx).w np (xxx).l np np + AddressingDispatch(CalcEffectiveAddress), - CalcEffectiveAddressIdleFor8bitDisplacement, // As per CalcEffectiveAddress unless one of the - // 8-bit displacement modes is in use, in which case - // an extra idle bus state is prefixed. + /// Similar to CalcEffectiveAddress, but varies slightly in the patterns: + /// + /// -(An) n + /// (d8, An, Xn) n np n + /// (d8, PC, Xn) n np n + AddressingDispatch(CalcEffectiveAddressIdleFor8bitDisplacementAndPreDec), // Various forms of perform; each of these will // perform the current instruction, then do the @@ -109,13 +110,6 @@ enum ExecutionState: int { Perform_np_n, Perform_np_nn, - MOVE, - MOVE_predec, - MOVE_predec_l, - MOVE_prefetch_decode, - MOVE_complete, - MOVE_complete_l, - TwoOp_Predec_bw, TwoOp_Predec_l, @@ -171,7 +165,6 @@ enum ExecutionState: int { DIVU_DIVS, Perform_idle_dyamic_Dn, LEA, - PEA, TAS, MOVEtoCCRSR, RTR, @@ -184,8 +177,26 @@ enum ExecutionState: int { STOP, TRAP, TRAPV, + + AddressRegisterIndirectWithIndex8bitDisplacement_n_np, + ProgramCounterIndirectWithIndex8bitDisplacement_n_np, + + AddressingDispatch(PEA), + PEA_np_nS_ns, // Used to complete (An), (d16, [An/PC]) and (d8, [An/PC], Xn). + PEA_np_nS_ns_np, // Used to complete (xxx).w and (xxx).l + + MOVE_b, MOVE_w, + AddressingDispatch(MOVE_bw), MOVE_bw_AbsoluteLong_prefetch_first, + AddressingDispatch(MOVE_l), MOVE_l_AbsoluteLong_prefetch_first, }; +#undef AddressingDispatch + +/// @returns The proper select lines for @c instruction's operand size, assuming it is either byte or word. +template Microcycle::OperationT data_select(const InstructionT &instruction) { + return Microcycle::OperationT(1 << int(instruction.operand_size())); +} + // MARK: - The state machine. template @@ -212,7 +223,15 @@ void Processor> 1) & 1)) { \ - bus_error_ = x; \ - exception_vector_ = berr_ ? InstructionSet::M68k::AccessFault : InstructionSet::M68k::AddressError; \ - MoveToStateSpecific(BusOrAddressErrorException); \ + if(berr_) { \ + RaiseBusOrAddressError(AccessFault, x); \ } \ if(vpa_) { \ x.length = HalfCycles(20) + (HalfCycles(20) + (e_clock_phase_ - time_remaining_) % HalfCycles(20)) % HalfCycles(20); \ @@ -288,13 +318,16 @@ void Processor> 1) & 1) { \ + RaiseBusOrAddressError(AddressError, perform); \ + } \ + PerformBusOperation(announce); \ + WaitForDTACK(announce); \ CompleteAccess(perform); // Sets up the next data access size and read flags. @@ -322,12 +355,6 @@ void Processor> 12].w))) + \ uint32_t(int8_t(prefetch_.b)); - BeginState(FetchAddressRegisterIndirectWithIndex8bitDisplacement_bw): + BeginStateMode(FetchOperand_bw, AddressRegisterIndirectWithIndex8bitDisplacement): effective_address_[next_operand_].l = d8Xn(registers_[8 + instruction_.reg(next_operand_)].l); SetDataAddress(effective_address_[next_operand_].l); @@ -1352,7 +1481,33 @@ void Processor +void Processor::reset() { + state_ = Reset; + time_remaining_ = HalfCycles(0); +} + } }