diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 0ee54c1d9..52dbe76d7 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1464,6 +1464,7 @@ 4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; 4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = ""; }; + 4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -3216,6 +3217,7 @@ children = ( 4BFF1D37223379D500838EA1 /* 68000Storage.hpp */, 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */, + 4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */, ); path = Implementation; sourceTree = ""; diff --git a/Processors/68000/68000.hpp b/Processors/68000/68000.hpp index 4357ba094..26087b9c8 100644 --- a/Processors/68000/68000.hpp +++ b/Processors/68000/68000.hpp @@ -10,42 +10,95 @@ #define MC68000_h #include +#include #include #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../RegisterSizes.hpp" namespace CPU { namespace MC68000 { +/*! + A microcycle is an atomic unit of 68000 bus activity — it is a single item large enough + fully to specify a sequence of bus events that occur without any possible interruption. + + Concretely, a standard read cycle breaks down into at least two microcycles: + + 1) a 5 half-cycle length microcycle in which the address strobe is signalled; and + 2) a 3 half-cycle length microcycle in which at least one of the data strobes is + signalled, and the data bus is sampled. + + That is, assuming DTack were signalled when microcycle (1) ended. If not then additional + wait state microcycles would fall between those two parts. + + The 68000 data sheet defines when the address becomes valid during microcycle (1), and + when the address strobe is actually asserted. But those timings are fixed. So simply + telling you that this was a microcycle during which the address trobe was signalled is + sufficient fully to describe the bus activity. + + (Aside: see the 68000 template's definition for options re: implicit DTack; if your + 68000 owner can always predict exactly how long it will hold DTack following observation + of an address-strobing microcycle, it can just supply those periods for accounting and + avoid the runtime cost of actual DTack emulation. But such as the bus allows.) +*/ struct Microcycle { - enum Operation { - Read16, - Write16, - ReadHigh, - ReadLow, - WriteHigh, - WriteLow, + /* + The operation code is a mask of all the signals that relevantly became active during + this microcycle. + */ + static const int Address = 1 << 0; + static const int UpperData = 1 << 1; + static const int LowerData = 1 << 2; + static const int ReadWrite = 1 << 3; // Set = read; unset = write. - /// The data bus is not tristated, but no data request is made. - Idle, + int operation = 0; + HalfCycles length = HalfCycles(2); - /// No data bus interaction at all; bus is tristated. - None - }; + /*! + For expediency, this provides a full 32-bit byte-resolution address — e.g. + if reading indirectly via an address register, this will indicate the full + value of the address register. - Operation operation = Operation::None; - Cycles length = Cycles(2); - uint32_t *address = nullptr; - uint16_t *value = nullptr; + The receiver should ignore bits 0 and 24+. + */ + const uint32_t *address = nullptr; + RegisterPair16 *value = nullptr; }; +/*! + This is the prototype for a 68000 bus handler; real bus handlers can descend from this + in order to get default implementations of any changes that may occur in the expected interface. +*/ class BusHandler { public: - Cycles perform_machine_cycle(const Microcycle &cycle) { - return Cycles(0); + static const int Data = 1 << 0; + static const int Program = 1 << 1; + static const int Supervisor = 1 << 2; + static const int Interrupt = Data | Program | Supervisor; + + /*! + Provides the bus handler with a single Microcycle to 'perform', along with the + contents of the FC0, FC1 and FC2 lines by way of processor_status. The symbols + above, Data, Program, Supervisor and Interrupt can be used to help to decode the + processor status if desired; in summary: + + If all three bits are set, this is an interrupt acknowledgement. + + If the data bit is set, the 68000 is fetching data. + + If the program bit is set, the 68000 is fetching instructions. + + It the supervisor bit is set, the 68000 is currently in supervisor mode. + + Neither program nor data being set has an undefined meaning. As does both program + and data being set, but this not being an interrupt. + */ + HalfCycles perform_bus_operation(const Microcycle &cycle, int processor_status) { + return HalfCycles(0); } - void flush(); + void flush() {} }; #include "Implementation/68000Storage.hpp" @@ -61,6 +114,8 @@ template class Processor: public ProcessorBase { T &bus_handler_; }; +#include "Implementation/68000Implementation.hpp" + } } diff --git a/Processors/68000/Implementation/68000Implementation.hpp b/Processors/68000/Implementation/68000Implementation.hpp new file mode 100644 index 000000000..59ff4bbd8 --- /dev/null +++ b/Processors/68000/Implementation/68000Implementation.hpp @@ -0,0 +1,10 @@ +// +// 68000Implementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +template void Processor::run_for(Cycles cycles) { +} diff --git a/Processors/68000/Implementation/68000Storage.cpp b/Processors/68000/Implementation/68000Storage.cpp index ec81695c9..3db0e75da 100644 --- a/Processors/68000/Implementation/68000Storage.cpp +++ b/Processors/68000/Implementation/68000Storage.cpp @@ -11,9 +11,14 @@ using namespace CPU::MC68000; ProcessorStorage::ProcessorStorage() { - reset_program_ = assemble_program("n- n- n- n- n- nn nF nV nv np np"); + reset_program_ = assemble_program("n- n- n- n- n- nn nF nf nV nv np np"); + + // Set initial state. Largely TODO. + active_program_ = reset_program_.data(); + effective_address_ = 0; } +// TODO: allow actions to be specified, of course. std::vector ProcessorStorage::assemble_program(const char *access_pattern) { std::vector steps; @@ -22,24 +27,85 @@ std::vector ProcessorStorage::assemble_program(const cha Step step; switch(*access_pattern) { - case ' ': break; // Space acts as a no-op; it's for clarity only. - - case 'n': // Nothing occurs; supply as 'None'. - step.microcycle.operation = Microcycle::Operation::None; + case '\t': case ' ': // White space acts as a no-op; it's for clarity only. + ++access_pattern; break; - case '-': // An idle cycle; distinct from a 'None'. - step.microcycle.operation = Microcycle::Operation::Idle; + case 'n': // This might be a plain NOP cycle, in which some internal calculation occurs, + // or it might pair off with something afterwards. + switch(access_pattern[1]) { + default: // This is probably a pure NOP; if what comes after this 'n' isn't actually + // valid, it should be caught in the outer switch the next time around the loop. + steps.push_back(step); + ++access_pattern; + break; + + case '-': // This is two NOPs in a row. + steps.push_back(step); + steps.push_back(step); + access_pattern += 2; + break; + + case 'F': // Fetch SSP MSW. + case 'f': // Fetch SSP LSW. + step.microcycle.length = HalfCycles(5); + step.microcycle.operation = Microcycle::Address | Microcycle::ReadWrite; + step.microcycle.address = &effective_address_; + step.microcycle.value = isupper(access_pattern[1]) ? &stack_pointers_[1].halves.high : &stack_pointers_[1].halves.low; + steps.push_back(step); + + step.microcycle.length = HalfCycles(3); + step.microcycle.operation |= Microcycle::LowerData | Microcycle::UpperData; + step.action = Step::Action::IncrementEffectiveAddress; + steps.push_back(step); + + access_pattern += 2; + break; + + case 'V': // Fetch exception vector low. + case 'v': // Fetch exception vector high. + step.microcycle.length = HalfCycles(5); + step.microcycle.operation = Microcycle::Address | Microcycle::ReadWrite; + step.microcycle.address = &effective_address_; + step.microcycle.value = isupper(access_pattern[1]) ? &program_counter_.halves.high : &program_counter_.halves.low; + steps.push_back(step); + + step.microcycle.length = HalfCycles(3); + step.microcycle.operation |= Microcycle::LowerData | Microcycle::UpperData; + step.action = Step::Action::IncrementEffectiveAddress; + steps.push_back(step); + + access_pattern += 2; + break; + + case 'p': // Fetch from the program counter into the prefetch queue. + step.microcycle.length = HalfCycles(5); + step.microcycle.operation = Microcycle::Address | Microcycle::ReadWrite; + step.microcycle.address = &program_counter_.full; + step.microcycle.value = &prefetch_queue_[1]; + step.action = Step::Action::AdvancePrefetch; + steps.push_back(step); + + step.microcycle.length = HalfCycles(3); + step.microcycle.operation |= Microcycle::LowerData | Microcycle::UpperData; + step.action = Step::Action::IncrementProgramCounter; + steps.push_back(step); + + access_pattern += 2; + break; + } break; - default: assert(false); + default: + std::cerr << "MC68000 program builder; Unknown access type " << *access_pattern << std::endl; + assert(false); } - - steps.push_back(step); - ++access_pattern; } - // TODO: add actions, somehow. + // Add a final 'ScheduleNextProgram' sentinel. + Step end_program; + end_program.action = Step::Action::ScheduleNextProgram; + steps.push_back(end_program); return steps; } diff --git a/Processors/68000/Implementation/68000Storage.hpp b/Processors/68000/Implementation/68000Storage.hpp index 2fc7b97a2..78279d075 100644 --- a/Processors/68000/Implementation/68000Storage.hpp +++ b/Processors/68000/Implementation/68000Storage.hpp @@ -14,29 +14,55 @@ class ProcessorStorage { ProcessorStorage(); protected: - uint32_t data_[8]; - uint32_t address_[7]; - uint32_t stack_pointers_[2]; - uint32_t program_counter_; + RegisterPair32 data_[8]; + RegisterPair32 address_[7]; + RegisterPair32 stack_pointers_[2]; // [0] = user stack pointer; [1] = supervisor + RegisterPair32 program_counter_; + + RegisterPair16 prefetch_queue_[2]; enum class State { Reset, Normal }; + // Generic sources and targets for memory operations. + uint32_t effective_address_; + RegisterPair32 bus_data_; + /*! A step is a microcycle to perform plus an action to occur afterwards, if any. */ struct Step { Microcycle microcycle; enum class Action { - None - } action_ = Action::None; + None, + + /// Performs effective_address_ += 2. + IncrementEffectiveAddress, + + /// Performs program_counter_ += 2. + IncrementProgramCounter, + + /// Copies prefetch_queue_[1] to prefetch_queue_[0]. + AdvancePrefetch, + + /*! + Terminates an atomic program; if nothing else is pending, schedules the next instruction. + This action is special in that it usurps any included microcycle. So any Step with this + as its action acts as an end-of-list sentinel. + */ + ScheduleNextProgram + + } action = Action::None; }; // Special programs. std::vector reset_program_; + // Current program pointer. + Step *active_program_ = nullptr; + private: enum class DataSize { Byte, Word, LongWord @@ -44,6 +70,46 @@ class ProcessorStorage { enum class AddressingMode { }; + /*! + Produces a vector of Steps that implement the described program. + + @param access_pattern A string describing the bus activity that occurs + during this program. This should follow the same general pattern as + those in yacht.txt; full description below. + + @discussion + The access pattern is defined, as in yacht.txt, to be a string consisting + of the following discrete bus actions. Spaces are ignored. + + * n: no operation; data bus is not used; + * -: idle state; data bus is not used but is also not available; + * p: program fetch; reads from the PC and adds two to it; + * W: write MSW of something onto the bus; + * w: write LSW of something onto the bus; + * R: read MSW of something from the bus; + * r: read LSW of soemthing from the bus; + * S: push the MSW of something onto the stack; + * s: push the LSW of something onto the stack; + * U: pop the MSW of something from the stack; + * u: pop the LSW of something from the stack; + * V: fetch a vector's MSW; + * v: fetch a vector's LSW; + * i: acquire interrupt vector in an IACK cycle; + * F: fetch the SSPs MSW; + * f: fetch the SSP's LSW. + + Quite a lot of that is duplicative, implying both something about internal + state and something about what's observable on the bus, but it's helpful to + stick to that document's coding exactly for easier debugging. + + p fetches will fill the prefetch queue, attaching an action to both the + step that precedes them and to themselves. The SSP fetches will go straight + to the SSP. + + Other actions will by default act via effective_address_ and bus_data_. + The user should fill in the steps necessary to get data into or extract + data from those. + */ std::vector assemble_program(const char *access_pattern); }; diff --git a/Processors/RegisterSizes.hpp b/Processors/RegisterSizes.hpp index 1252b2634..2cb9af172 100644 --- a/Processors/RegisterSizes.hpp +++ b/Processors/RegisterSizes.hpp @@ -32,7 +32,7 @@ template union RegisterPair { }; typedef RegisterPair RegisterPair16; -typedef RegisterPair RegisterPair32; +typedef RegisterPair RegisterPair32; }