mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Embraces a more communicative 68000 bus.
This commit is contained in:
parent
4aeb9a7c56
commit
de56d48b2f
@ -1464,6 +1464,7 @@
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -3216,6 +3217,7 @@
|
||||
children = (
|
||||
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */,
|
||||
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */,
|
||||
4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
|
@ -10,42 +10,95 @@
|
||||
#define MC68000_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#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 T> class Processor: public ProcessorBase {
|
||||
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;
|
||||
|
||||
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<Step> steps;
|
||||
|
||||
@ -22,24 +27,85 @@ std::vector<ProcessorStorage::Step> 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;
|
||||
}
|
||||
|
@ -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<Step> 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<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<uint32_t, uint16_t> RegisterPair32;
|
||||
typedef RegisterPair<uint32_t, RegisterPair16> RegisterPair32;
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user