mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-09 05:25:01 +00:00
Embraces a more communicative 68000 bus.
This commit is contained in:
@@ -1464,6 +1464,7 @@
|
|||||||
4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
||||||
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
|
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
|
||||||
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = "<group>"; };
|
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = "<group>"; };
|
||||||
|
4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -3216,6 +3217,7 @@
|
|||||||
children = (
|
children = (
|
||||||
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */,
|
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */,
|
||||||
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */,
|
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */,
|
||||||
|
4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */,
|
||||||
);
|
);
|
||||||
path = Implementation;
|
path = Implementation;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@@ -10,42 +10,95 @@
|
|||||||
#define MC68000_h
|
#define MC68000_h
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
#include "../RegisterSizes.hpp"
|
||||||
|
|
||||||
namespace CPU {
|
namespace CPU {
|
||||||
namespace MC68000 {
|
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 {
|
struct Microcycle {
|
||||||
enum Operation {
|
/*
|
||||||
Read16,
|
The operation code is a mask of all the signals that relevantly became active during
|
||||||
Write16,
|
this microcycle.
|
||||||
ReadHigh,
|
*/
|
||||||
ReadLow,
|
static const int Address = 1 << 0;
|
||||||
WriteHigh,
|
static const int UpperData = 1 << 1;
|
||||||
WriteLow,
|
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.
|
int operation = 0;
|
||||||
Idle,
|
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;
|
The receiver should ignore bits 0 and 24+.
|
||||||
Cycles length = Cycles(2);
|
*/
|
||||||
uint32_t *address = nullptr;
|
const uint32_t *address = nullptr;
|
||||||
uint16_t *value = 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 {
|
class BusHandler {
|
||||||
public:
|
public:
|
||||||
Cycles perform_machine_cycle(const Microcycle &cycle) {
|
static const int Data = 1 << 0;
|
||||||
return Cycles(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"
|
#include "Implementation/68000Storage.hpp"
|
||||||
@@ -61,6 +114,8 @@ template <class T> class Processor: public ProcessorBase {
|
|||||||
T &bus_handler_;
|
T &bus_handler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#include "Implementation/68000Implementation.hpp"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
Processors/68000/Implementation/68000Implementation.hpp
Normal file
10
Processors/68000/Implementation/68000Implementation.hpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//
|
||||||
|
// 68000Implementation.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 10/03/2019.
|
||||||
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
template <class T> void Processor<T>::run_for(Cycles cycles) {
|
||||||
|
}
|
@@ -11,9 +11,14 @@
|
|||||||
using namespace CPU::MC68000;
|
using namespace CPU::MC68000;
|
||||||
|
|
||||||
ProcessorStorage::ProcessorStorage() {
|
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::Step> ProcessorStorage::assemble_program(const char *access_pattern) {
|
std::vector<ProcessorStorage::Step> ProcessorStorage::assemble_program(const char *access_pattern) {
|
||||||
std::vector<Step> steps;
|
std::vector<Step> steps;
|
||||||
|
|
||||||
@@ -22,24 +27,85 @@ std::vector<ProcessorStorage::Step> ProcessorStorage::assemble_program(const cha
|
|||||||
Step step;
|
Step step;
|
||||||
|
|
||||||
switch(*access_pattern) {
|
switch(*access_pattern) {
|
||||||
case ' ': break; // Space acts as a no-op; it's for clarity only.
|
case '\t': case ' ': // White space acts as a no-op; it's for clarity only.
|
||||||
|
++access_pattern;
|
||||||
case 'n': // Nothing occurs; supply as 'None'.
|
|
||||||
step.microcycle.operation = Microcycle::Operation::None;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '-': // An idle cycle; distinct from a 'None'.
|
case 'n': // This might be a plain NOP cycle, in which some internal calculation occurs,
|
||||||
step.microcycle.operation = Microcycle::Operation::Idle;
|
// 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;
|
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;
|
return steps;
|
||||||
}
|
}
|
||||||
|
@@ -14,29 +14,55 @@ class ProcessorStorage {
|
|||||||
ProcessorStorage();
|
ProcessorStorage();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint32_t data_[8];
|
RegisterPair32 data_[8];
|
||||||
uint32_t address_[7];
|
RegisterPair32 address_[7];
|
||||||
uint32_t stack_pointers_[2];
|
RegisterPair32 stack_pointers_[2]; // [0] = user stack pointer; [1] = supervisor
|
||||||
uint32_t program_counter_;
|
RegisterPair32 program_counter_;
|
||||||
|
|
||||||
|
RegisterPair16 prefetch_queue_[2];
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
Reset,
|
Reset,
|
||||||
Normal
|
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.
|
A step is a microcycle to perform plus an action to occur afterwards, if any.
|
||||||
*/
|
*/
|
||||||
struct Step {
|
struct Step {
|
||||||
Microcycle microcycle;
|
Microcycle microcycle;
|
||||||
enum class Action {
|
enum class Action {
|
||||||
None
|
None,
|
||||||
} action_ = Action::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.
|
// Special programs.
|
||||||
std::vector<Step> reset_program_;
|
std::vector<Step> reset_program_;
|
||||||
|
|
||||||
|
// Current program pointer.
|
||||||
|
Step *active_program_ = nullptr;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class DataSize {
|
enum class DataSize {
|
||||||
Byte, Word, LongWord
|
Byte, Word, LongWord
|
||||||
@@ -44,6 +70,46 @@ class ProcessorStorage {
|
|||||||
enum class AddressingMode {
|
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<Step> assemble_program(const char *access_pattern);
|
std::vector<Step> assemble_program(const char *access_pattern);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@ template <typename Full, typename Half> union RegisterPair {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef RegisterPair<uint16_t, uint8_t> RegisterPair16;
|
typedef RegisterPair<uint16_t, uint8_t> RegisterPair16;
|
||||||
typedef RegisterPair<uint32_t, uint16_t> RegisterPair32;
|
typedef RegisterPair<uint32_t, RegisterPair16> RegisterPair32;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user