1
0
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:
Thomas Harte 2019-03-10 17:27:34 -04:00
parent 4aeb9a7c56
commit de56d48b2f
6 changed files with 237 additions and 38 deletions

View File

@ -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>";

View File

@ -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"
}
}

View 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) {
}

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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;
}