1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-09 00:37:27 +00:00

Eliminate the old 68000 implementation.

This commit is contained in:
Thomas Harte 2023-05-10 17:06:27 -05:00
parent 2b56b7be0d
commit e56db3c4e5
10 changed files with 3 additions and 7189 deletions

View File

@ -34,7 +34,6 @@
#include "../../../Components/AppleClock/AppleClock.hpp"
#include "../../../Components/DiskII/IWM.hpp"
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
#include "../../../Processors/68000/68000.hpp"
#include "../../../Processors/68000Mk2/68000Mk2.hpp"

View File

@ -168,8 +168,6 @@
4B1A1B1F27320FBC00119335 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1A1B1C27320FBB00119335 /* Disk.cpp */; };
4B1B58F6246CC4E8009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; };
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; };
4B1B58FF246E19FD009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58FD246E19FD009C171E /* State.cpp */; };
4B1B5900246E19FD009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58FD246E19FD009C171E /* State.cpp */; };
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; };
@ -352,7 +350,6 @@
4B7752C128217F490073E2C5 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
4B7752C228217F5C0073E2C5 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; };
4B7752C328217F720073E2C5 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
@ -1080,8 +1077,6 @@
4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; };
4BFEA2EF2682A7B900EBF94C /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; };
4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; };
4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */; };
/* End PBXBuildFile section */
@ -1228,8 +1223,6 @@
4B1A1B1C27320FBB00119335 /* Disk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Disk.cpp; sourceTree = "<group>"; };
4B1B58F4246CC4E8009C171E /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4B1B58F5246CC4E8009C171E /* State.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4B1B58FD246E19FD009C171E /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4B1B58FE246E19FD009C171E /* State.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiKeyboardMachine.cpp; sourceTree = "<group>"; };
4B1B88BA202E2EC100B67DFF /* MultiKeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiKeyboardMachine.hpp; sourceTree = "<group>"; };
4B1B88BE202E3DB200B67DFF /* MultiConfigurable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiConfigurable.cpp; sourceTree = "<group>"; };
@ -2248,10 +2241,6 @@
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Dave.cpp; sourceTree = "<group>"; };
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Dave.hpp; sourceTree = "<group>"; };
4BFEA2F12682A90200EBF94C /* Sizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Sizes.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>"; };
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>"; };
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EmuTOSTests.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -2548,15 +2537,6 @@
path = State;
sourceTree = "<group>";
};
4B1B58FC246E19FD009C171E /* State */ = {
isa = PBXGroup;
children = (
4B1B58FD246E19FD009C171E /* State.cpp */,
4B1B58FE246E19FD009C171E /* State.hpp */,
);
path = State;
sourceTree = "<group>";
};
4B1E85791D174DEC001EF87D /* 6532 */ = {
isa = PBXGroup;
children = (
@ -4390,7 +4370,6 @@
4B1414561B58879D00E04248 /* 6502 */,
4B4DEC15252BFA9C004583AC /* 6502Esque */,
4BF8D4CC251C0C9C00BBE21B /* 65816 */,
4BFF1D332233778C00838EA1 /* 68000 */,
4BCA2F552832A643006C632A /* 68000Mk2 */,
4B77069E1EC9045B0053B588 /* Z80 */,
);
@ -5033,26 +5012,6 @@
path = Utility;
sourceTree = "<group>";
};
4BFF1D332233778C00838EA1 /* 68000 */ = {
isa = PBXGroup;
children = (
4BFF1D342233778C00838EA1 /* 68000.hpp */,
4BFF1D36223379D500838EA1 /* Implementation */,
4B1B58FC246E19FD009C171E /* State */,
);
path = 68000;
sourceTree = "<group>";
};
4BFF1D36223379D500838EA1 /* Implementation */ = {
isa = PBXGroup;
children = (
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */,
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */,
4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */,
);
path = Implementation;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -5579,7 +5538,6 @@
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */,
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
4B1B5900246E19FD009C171E /* State.cpp in Sources */,
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */,
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
4B43983B29620FC9006B0BFC /* 9918.cpp in Sources */,
@ -5636,7 +5594,6 @@
4B05401F219D1618001BF69C /* ScanTarget.cpp in Sources */,
4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */,
4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */,
4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */,
4B8318B722D3E54D006DB630 /* Video.cpp in Sources */,
4B7C681F2751A104001671EC /* Bitplanes.cpp in Sources */,
4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */,
@ -5919,7 +5876,6 @@
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B1B58FF246E19FD009C171E /* State.cpp in Sources */,
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
4B9EC0EA26B384080060A31F /* Keyboard.cpp in Sources */,
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
@ -6061,7 +6017,6 @@
4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */,
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */,
4B1B88C0202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */,
4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */,
4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */,
4BB244D522AABAF600BE20E5 /* z8530.cpp in Sources */,
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
@ -6159,7 +6114,6 @@
4B7752C128217F490073E2C5 /* FAT.cpp in Sources */,
4B778F5B23A5F2DE0000D260 /* Tape.cpp in Sources */,
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */,
4B7752B228217EAE0073E2C5 /* StaticAnalyser.cpp in Sources */,
4B7752BC28217F1D0073E2C5 /* Disk.cpp in Sources */,
4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */,

View File

@ -9,7 +9,6 @@
#import <XCTest/XCTest.h>
#include "68000.hpp"
#include "68000Mk2.hpp"
#include <array>
@ -17,6 +16,7 @@
#include <unordered_set>
#include <set>
/*
namespace {
struct RandomStore {
@ -694,3 +694,4 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
}
@end
*/

View File

@ -16,7 +16,7 @@
#include <zlib.h>
#include "68000.hpp"
#include "68000Mk2.hpp"
#include "Comparative68000.hpp"
#include "CSROMFetcher.hpp"

View File

@ -1,474 +0,0 @@
//
// 68000.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MC68000_h
#define MC68000_h
#include <cassert>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <limits>
#include <ostream>
#include <vector>
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Numeric/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 4 half-cycle length microcycle in which the address strobe is signalled; and
2) a 4 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 {
using OperationT = unsigned int;
/// Indicates that the address strobe and exactly one of the data strobes are active; you can determine
/// which by inspecting the low bit of the provided address. The RW line indicates a read.
static constexpr OperationT SelectByte = 1 << 0;
// Maintenance note: this is bit 0 to reduce the cost of getting a host-endian
// bytewise address. The assumption that it is bit 0 is also used for branchless
// selection in a few places. See implementation of host_endian_byte_address(),
// value8_high(), value8_low() and value16().
/// Indicates that the address and both data select strobes are active.
static constexpr OperationT SelectWord = 1 << 1;
/// If set, indicates a read. Otherwise, a write.
static constexpr OperationT Read = 1 << 2;
// Two-bit gap deliberately left here for PermitRead/Write below.
/// A NewAddress cycle is one in which the address strobe is initially low but becomes high;
/// this correlates to states 0 to 5 of a standard read/write cycle.
static constexpr OperationT NewAddress = 1 << 5;
/// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither
/// of the data strobes are.
static constexpr OperationT SameAddress = 1 << 6;
/// A Reset cycle is one in which the RESET output is asserted.
static constexpr OperationT Reset = 1 << 7;
/// Contains the value of line FC0 if it is not implicit via InterruptAcknowledge.
static constexpr OperationT IsData = 1 << 8;
/// Contains the value of line FC1 if it is not implicit via InterruptAcknowledge.
static constexpr OperationT IsProgram = 1 << 9;
/// The interrupt acknowledge cycle is that during which the 68000 seeks to obtain the vector for
/// an interrupt it plans to observe. Noted on a real 68000 by all FCs being set to 1.
static constexpr OperationT InterruptAcknowledge = 1 << 10;
/// Represents the state of the 68000's valid memory address line — indicating whether this microcycle
/// is synchronised with the E clock to satisfy a valid peripheral address request.
static constexpr OperationT IsPeripheral = 1 << 11;
/// Provides the 68000's bus grant line — indicating whether a bus request has been acknowledged.
static constexpr OperationT BusGrant = 1 << 12;
/// Contains a valid combination of the various static constexpr int flags, describing the operation
/// performed by this Microcycle.
OperationT operation = 0;
/// Describes the duration of this Microcycle.
HalfCycles length = HalfCycles(4);
/*!
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.
The receiver should ignore bits 0 and 24+. Use word_address() automatically
to obtain the only the 68000's real address lines, giving a 23-bit address
at word resolution.
*/
const uint32_t *address = nullptr;
/*!
If this is a write cycle, dereference value to get the value loaded onto
the data bus.
If this is a read cycle, write the value on the data bus to it.
Otherwise, this value is undefined.
Byte values are provided via @c value.halves.low. @c value.halves.high is undefined.
This is true regardless of whether the upper or lower byte of a word is being
accessed.
Word values occupy the entirety of @c value.full.
*/
RegisterPair16 *value = nullptr;
/// @returns @c true if two Microcycles are equal; @c false otherwise.
bool operator ==(const Microcycle &rhs) const {
if(value != rhs.value) return false;
if(address != rhs.address) return false;
if(length != rhs.length) return false;
if(operation != rhs.operation) return false;
return true;
}
// Various inspectors.
/*! @returns true if any data select line is active; @c false otherwise. */
forceinline bool data_select_active() const {
return bool(operation & (SelectWord | SelectByte | InterruptAcknowledge));
}
/*!
@returns 0 if this byte access wants the low part of a 16-bit word; 8 if it wants the high part.
*/
forceinline unsigned int byte_shift() const {
return (((*address) & 1) << 3) ^ 8;
}
/*!
Obtains the mask to apply to a word that will leave only the byte this microcycle is selecting.
@returns 0x00ff if this byte access wants the low part of a 16-bit word; 0xff00 if it wants the high part.
*/
forceinline uint16_t byte_mask() const {
return uint16_t(0xff00) >> (((*address) & 1) << 3);
}
/*!
Obtains the mask to apply to a word that will leave only the byte this microcycle **isn't** selecting.
i.e. this is the part of a word that should be untouched by this microcycle.
@returns 0xff00 if this byte access wants the low part of a 16-bit word; 0x00ff if it wants the high part.
*/
forceinline uint16_t untouched_byte_mask() const {
return uint16_t(uint16_t(0xff) << (((*address) & 1) << 3));
}
/*!
Assuming this cycle is a byte write, mutates @c destination by writing the byte to the proper upper or
lower part, retaining the other half.
*/
forceinline uint16_t write_byte(uint16_t destination) const {
return uint16_t((destination & untouched_byte_mask()) | (value->halves.low << byte_shift()));
}
/*!
@returns non-zero if this is a byte read and 68000 LDS is asserted.
*/
forceinline int lower_data_select() const {
return (operation & SelectByte) & ((*address & 1) << 3);
}
/*!
@returns non-zero if this is a byte read and 68000 UDS is asserted.
*/
forceinline int upper_data_select() const {
return (operation & SelectByte) & ~((*address & 1) << 3);
}
/*!
@returns the address being accessed at the precision a 68000 supplies it
only 24 address bit precision, with the low bit shifted out. So it's the
68000 address at word precision: address 0 is the first word in the address
space, address 1 is the second word (i.e. the third and fourth bytes) in
the address space, etc.
*/
forceinline uint32_t word_address() const {
return (address ? (*address) & 0x00fffffe : 0) >> 1;
}
/*!
@returns the address of the word or byte being accessed at byte precision,
in the endianness of the host platform.
So: if this is a word access, and the 68000 wants to select the word at address
@c n, this will evaluate to @c n regardless of the host machine's endianness..
If this is a byte access and the host machine is big endian it will evalue to @c n.
If the host machine is little endian then it will evaluate to @c n^1.
*/
forceinline uint32_t host_endian_byte_address() const {
#if TARGET_RT_BIG_ENDIAN
return *address & 0xffffff;
#else
return (*address ^ (1 & operation & SelectByte)) & 0xffffff;
#endif
}
/*!
@returns the value on the data bus all 16 bits, with any inactive lines
(as er the upper and lower data selects) being represented by 1s. Assumes
this is a write cycle.
*/
forceinline uint16_t value16() const {
const uint16_t values[] = { value->full, uint16_t((value->halves.low << 8) | value->halves.low) };
return values[operation & SelectByte];
}
/*!
@returns the value currently on the high 8 lines of the data bus if any;
@c 0xff otherwise. Assumes this is a write cycle.
*/
forceinline uint8_t value8_high() const {
const uint8_t values[] = { uint8_t(value->full >> 8), value->halves.low};
return values[operation & SelectByte];
}
/*!
@returns the value currently on the low 8 lines of the data bus if any;
@c 0xff otherwise. Assumes this is a write cycle.
*/
forceinline uint8_t value8_low() const {
const uint8_t values[] = { uint8_t(value->full), value->halves.low};
return values[operation & SelectByte];
}
/*!
Sets to @c value the 8- or 16-bit portion of the supplied value that is
currently being read. Assumes this is a read cycle.
*/
forceinline void set_value16(uint16_t v) const {
assert(operation & Microcycle::Read);
if(operation & Microcycle::SelectWord) {
value->full = v;
} else {
value->halves.low = uint8_t(v >> byte_shift());
}
}
/*!
Equivalent to set_value16((v << 8) | 0x00ff).
*/
forceinline void set_value8_high(uint8_t v) const {
assert(operation & Microcycle::Read);
if(operation & Microcycle::SelectWord) {
value->full = uint16_t(0x00ff | (v << 8));
} else {
value->halves.low = uint8_t(v | (0xff00 >> ((*address & 1) << 3)));
}
}
/*!
Equivalent to set_value16((v) | 0xff00).
*/
forceinline void set_value8_low(uint8_t v) const {
assert(operation & Microcycle::Read);
if(operation & Microcycle::SelectWord) {
value->full = 0xff00 | v;
} else {
value->halves.low = uint8_t(v | (0x00ff << ((*address & 1) << 3)));
}
}
/*!
@returns the same value as word_address() for any Microcycle with the NewAddress or
SameAddress flags set; undefined behaviour otherwise.
*/
forceinline uint32_t active_operation_word_address() const {
return ((*address) & 0x00fffffe) >> 1;
}
// PermitRead and PermitWrite are used as part of the read/write mask
// supplied to @c apply; they are picked to be small enough values that
// a byte can be used for storage.
static constexpr OperationT PermitRead = 1 << 3;
static constexpr OperationT PermitWrite = 1 << 4;
/*!
Assuming this to be a cycle with a data select active, applies it to @c target
subject to the read_write_mask, where 'applies' means:
* if this is a byte read, reads a single byte from @c target;
* if this is a word read, reads a word (in the host platform's endianness) from @c target; and
* if this is a write, does the converse of a read.
*/
forceinline void apply(uint8_t *target, OperationT read_write_mask = PermitRead | PermitWrite) const {
assert( (operation & (SelectWord | SelectByte)) != (SelectWord | SelectByte));
switch((operation | read_write_mask) & (SelectWord | SelectByte | Read | PermitRead | PermitWrite)) {
default:
break;
case SelectWord | Read | PermitRead:
case SelectWord | Read | PermitRead | PermitWrite:
value->full = *reinterpret_cast<uint16_t *>(target);
break;
case SelectByte | Read | PermitRead:
case SelectByte | Read | PermitRead | PermitWrite:
value->halves.low = *target;
break;
case SelectWord | PermitWrite:
case SelectWord | PermitWrite | PermitRead:
*reinterpret_cast<uint16_t *>(target) = value->full;
break;
case SelectByte | PermitWrite:
case SelectByte | PermitWrite | PermitRead:
*target = value->halves.low;
break;
}
}
#ifndef NDEBUG
bool is_resizeable = false;
#endif
};
/*!
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:
/*!
Provides the bus handler with a single Microcycle to 'perform'.
FC0 and FC1 are provided inside the microcycle as the IsData and IsProgram
flags; FC2 is provided here as is_supervisor it'll be either 0 or 1.
*/
HalfCycles perform_bus_operation([[maybe_unused]] const Microcycle &cycle, [[maybe_unused]] int is_supervisor) {
return HalfCycles(0);
}
/*!
Provides information about the path of execution if enabled via the template.
*/
void will_perform([[maybe_unused]] uint32_t address, [[maybe_unused]] uint16_t opcode) {}
};
#include "Implementation/68000Storage.hpp"
class ProcessorBase: public ProcessorStorage {
};
enum Flag: uint16_t {
Trace = 0x8000,
Supervisor = 0x2000,
ConditionCodes = 0x1f,
Extend = 0x0010,
Negative = 0x0008,
Zero = 0x0004,
Overflow = 0x0002,
Carry = 0x0001
};
struct ProcessorState {
uint32_t data[8];
uint32_t address[7];
uint32_t user_stack_pointer, supervisor_stack_pointer;
uint32_t program_counter;
uint16_t status;
/*!
@returns the supervisor stack pointer if @c status indicates that
the processor is in supervisor mode; the user stack pointer otherwise.
*/
uint32_t stack_pointer() const {
return (status & Flag::Supervisor) ? supervisor_stack_pointer : user_stack_pointer;
}
// TODO: More state needed to indicate current instruction, the processor's
// progress through it, and anything it has fetched so far.
// uint16_t current_instruction;
};
template <class T, bool dtack_is_implicit, bool signal_will_perform = false> class Processor: public ProcessorBase {
public:
Processor(T &bus_handler) : ProcessorBase(), bus_handler_(bus_handler) {}
void run_for(HalfCycles duration);
using State = ProcessorState;
/// @returns The current processor state.
State get_state();
/// Sets the processor to the supplied state.
void set_state(const State &);
/// Sets the DTack line — @c true for active, @c false for inactive.
inline void set_dtack(bool dtack) {
dtack_ = dtack;
}
/// Sets the VPA (valid peripheral address) line — @c true for active, @c false for inactive.
inline void set_is_peripheral_address(bool is_peripheral_address) {
is_peripheral_address_ = is_peripheral_address;
}
/// Sets the bus error line — @c true for active, @c false for inactive.
inline void set_bus_error(bool bus_error) {
bus_error_ = bus_error;
}
/// Sets the interrupt lines, IPL0, IPL1 and IPL2.
inline void set_interrupt_level(int interrupt_level) {
bus_interrupt_level_ = interrupt_level;
}
/// Sets the bus request line.
/// This area of functionality is TODO.
inline void set_bus_request(bool bus_request) {
bus_request_ = bus_request;
}
/// Sets the bus acknowledge line.
/// This area of functionality is TODO.
inline void set_bus_acknowledge(bool bus_acknowledge) {
bus_acknowledge_ = bus_acknowledge;
}
/// Sets the halt line.
inline void set_halt(bool halt) {
halt_ = halt;
}
/// @returns The current phase of the E clock; this will be a number of
/// half-cycles between 0 and 19 inclusive, indicating how far the 68000
/// is into the current E cycle.
///
/// This is guaranteed to be 0 at initial 68000 construction.
HalfCycles get_e_clock_phase() {
return e_clock_phase_;
}
void reset();
private:
T &bus_handler_;
};
#include "Implementation/68000Implementation.hpp"
}
}
#endif /* MC68000_h */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,579 +0,0 @@
//
// 68000Storage.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MC68000Storage_h
#define MC68000Storage_h
class ProcessorStorage {
public:
ProcessorStorage();
protected:
RegisterPair32 data_[8];
RegisterPair32 address_[8];
RegisterPair32 program_counter_;
RegisterPair32 stack_pointers_[2]; // [0] = user stack pointer; [1] = supervisor; the values from here
// are copied into/out of address_[7] upon mode switches.
RegisterPair32 prefetch_queue_; // Each word will go into the low part of the word, then proceed upward.
// Generic sources and targets for memory operations;
// by convention: effective_address_[0] = source, [1] = destination.
//
// These, and the various register contents above, should be kept
// close to the top of this class so that small-integer offsets can be
// used in instances of Program (see below).
RegisterPair32 effective_address_[2];
RegisterPair32 source_bus_data_;
RegisterPair32 destination_bus_data_;
enum class ExecutionState {
/// The normal mode, this means the 68000 is expending processing effort.
Executing,
/// The 68000 is in a holding loop, waiting for either DTack or to be notified of a bus error.
WaitingForDTack,
/// Occurs after executing a STOP instruction; the processor will idle waiting for an interrupt or reset.
Stopped,
/// Occurs at the end of the current bus cycle after detection of the HALT input, continuing until
/// HALT is no longer signalled.
Halted,
/// Signals a transition from some other straight directly to cueing up an interrupt.
WillBeginInterrupt,
} execution_state_ = ExecutionState::Executing;
Microcycle dtack_cycle_;
Microcycle stop_cycle_;
// Various status parts.
int is_supervisor_;
int interrupt_level_;
uint_fast32_t zero_result_; // The zero flag is set if this value is zero.
uint_fast32_t carry_flag_; // The carry flag is set if this value is non-zero.
uint_fast32_t extend_flag_; // The extend flag is set if this value is non-zero.
uint_fast32_t overflow_flag_; // The overflow flag is set if this value is non-zero.
uint_fast32_t negative_flag_; // The negative flag is set if this value is non-zero.
uint_fast32_t trace_flag_; // The trace flag is set if this value is non-zero.
uint_fast32_t last_trace_flag_ = 0;
// Bus inputs.
int bus_interrupt_level_ = 0;
bool dtack_ = false;
bool is_peripheral_address_ = false;
bool bus_error_ = false;
bool bus_request_ = false;
bool bus_acknowledge_ = false;
bool halt_ = false;
// Holds the interrupt level that should be serviced at the next instruction
// dispatch, if any.
int pending_interrupt_level_ = 0;
// Holds the interrupt level that is currently being serviced.
// TODO: surely this doesn't need to be distinct from the pending_interrupt_level_?
int accepted_interrupt_level_ = 0;
bool is_starting_interrupt_ = false;
HalfCycles half_cycles_left_to_run_;
HalfCycles e_clock_phase_;
enum class Operation: uint8_t {
None,
ABCD, SBCD, NBCD,
ADDb, ADDw, ADDl,
ADDQb, ADDQw, ADDQl,
ADDAw, ADDAl,
ADDQAw, ADDQAl,
ADDXb, ADDXw, ADDXl,
SUBb, SUBw, SUBl,
SUBQb, SUBQw, SUBQl,
SUBAw, SUBAl,
SUBQAw, SUBQAl,
SUBXb, SUBXw, SUBXl,
MOVEb, MOVEw, MOVEl, MOVEq,
MOVEAw, MOVEAl,
PEA,
MOVEtoSR, MOVEfromSR,
MOVEtoCCR,
ORItoSR, ORItoCCR,
ANDItoSR, ANDItoCCR,
EORItoSR, EORItoCCR,
BTSTb, BTSTl,
BCLRl, BCLRb,
CMPb, CMPw, CMPl,
CMPAw,
TSTb, TSTw, TSTl,
JMP, RTS,
BRA, Bcc,
DBcc,
Scc,
CLRb, CLRw, CLRl,
NEGXb, NEGXw, NEGXl,
NEGb, NEGw, NEGl,
ASLb, ASLw, ASLl, ASLm,
ASRb, ASRw, ASRl, ASRm,
LSLb, LSLw, LSLl, LSLm,
LSRb, LSRw, LSRl, LSRm,
ROLb, ROLw, ROLl, ROLm,
RORb, RORw, RORl, RORm,
ROXLb, ROXLw, ROXLl, ROXLm,
ROXRb, ROXRw, ROXRl, ROXRm,
MOVEMtoRl, MOVEMtoRw,
MOVEMtoMl, MOVEMtoMw,
MOVEPtoRl, MOVEPtoRw,
MOVEPtoMl, MOVEPtoMw,
ANDb, ANDw, ANDl,
EORb, EORw, EORl,
NOTb, NOTw, NOTl,
ORb, ORw, ORl,
MULU, MULS,
DIVU, DIVS,
RTE_RTR,
TRAP, TRAPV,
CHK,
EXG, SWAP,
BCHGl, BCHGb,
BSETl, BSETb,
TAS,
EXTbtow, EXTwtol,
LINK, UNLINK,
STOP,
};
/*!
Bus steps are sequences of things to communicate to the bus.
Standard behaviour is: (i) perform microcycle; (ii) perform action.
*/
struct BusStep {
Microcycle microcycle;
enum class Action {
None,
/// Performs effective_address_[0] += 2.
IncrementEffectiveAddress0,
/// Performs effective_address_[1] += 2.
IncrementEffectiveAddress1,
/// Performs effective_address_[0] -= 2.
DecrementEffectiveAddress0,
/// Performs effective_address_[1] -= 2.
DecrementEffectiveAddress1,
/// Performs program_counter_ += 2.
IncrementProgramCounter,
/// Copies prefetch_queue_[1] to prefetch_queue_[0].
AdvancePrefetch,
/// Performs effective_address_[0] += 2 and zeroes the final bit of the stack pointer.
IncrementEffectiveAddress0AlignStackPointer,
/*!
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;
forceinline bool operator ==(const BusStep &rhs) const {
if(action != rhs.action) return false;
return microcycle == rhs.microcycle;
}
forceinline bool is_terminal() const {
return action == Action::ScheduleNextProgram;
}
};
/*!
A micro-op is: (i) an action to take; and (ii) a sequence of bus operations
to perform after taking the action.
NOTE: this therefore has the opposite order of behaviour compared to a BusStep,
the action occurs BEFORE the bus operations, not after.
A nullptr bus_program terminates a sequence of micro operations; the is_terminal
test should be used to query for that. The action on the final operation will
be performed.
*/
struct MicroOp {
enum class Action: uint8_t {
None,
/// Does whatever this instruction says is the main operation.
PerformOperation,
/*
All of the below will honour the source and destination masks
in deciding where to apply their actions.
*/
/// Subtracts 1 from the [source/destination]_address.
Decrement1,
/// Subtracts 2 from the [source/destination]_address.
Decrement2,
/// Subtracts 4 from the [source/destination]_address.
Decrement4,
/// Adds 1 from the [source/destination]_address.
Increment1,
/// Adds 2 from the [source/destination]_address.
Increment2,
/// Adds 4 from the [source/destination]_address.
Increment4,
/// Copies the source and/or destination to effective_address_.
CopyToEffectiveAddress,
/// Peeking into the end of the prefetch queue, calculates the proper target of (d16,An) addressing.
CalcD16An,
/// Peeking into the end of the prefetch queue, calculates the proper target of (d8,An,Xn) addressing.
CalcD8AnXn,
/// Peeking into the prefetch queue, calculates the proper target of (d16,PC) addressing,
/// adjusting as though it had been performed after the proper PC fetches. The source
/// and destination mask flags affect only the destination of the result.
CalcD16PC,
/// Peeking into the prefetch queue, calculates the proper target of (d8,An,Xn) addressing,
/// adjusting as though it had been performed after the proper PC fetches. The source
/// and destination mask flags affect only the destination of the result.
CalcD8PCXn,
/// Sets the high word according to the MSB of the low word.
SignExtendWord,
/// Sets the high three bytes according to the MSB of the low byte.
SignExtendByte,
/// From the next word in the prefetch queue assembles a sign-extended long word in either or
/// both of effective_address_[0] and effective_address_[1].
AssembleWordAddressFromPrefetch,
/// From the next word in the prefetch queue assembles a 0-padded 32-bit long word in either or
/// both of bus_data_[0] and bus_data_[1].
AssembleWordDataFromPrefetch,
/// Copies the next two prefetch words into one of the effective_address_.
AssembleLongWordAddressFromPrefetch,
/// Copies the next two prefetch words into one of the bus_data_.
AssembleLongWordDataFromPrefetch,
/// Copies the low part of the prefetch queue into next_word_.
CopyNextWord,
/// Performs write-back of post-increment address and/or sign extensions as necessary.
MOVEMtoRComplete,
/// Performs write-back of pre-decrement address.
MOVEMtoMComplete,
// (i) inspects the prefetch queue to determine the length of this instruction and copies the next PC to destination_bus_data_;
// (ii) copies the stack pointer minus 4 to effective_address_[1];
// (iii) decrements the stack pointer by four.
PrepareJSR,
PrepareBSR,
// (i) copies the stack pointer to effective_address_[0];
// (ii) increments the stack pointer by four.
PrepareRTS,
// (i) fills in the proper stack addresses to the bus steps for this micro-op; and
// (ii) adjusts the stack pointer appropriately.
PrepareRTE_RTR,
// Performs the necessary status word substitution for the current interrupt level,
// and does the first part of initialising the trap steps.
PrepareINT,
// Observes the bus_error_, valid_peripheral_address_ and/or the value currently in
// source_bus_data_ to determine an interrupt vector, and fills in the final trap
// steps detail appropriately.
PrepareINTVector,
};
static_assert(uint8_t(Action::PrepareINTVector) < 32); // i.e. will fit into five bits.
static constexpr int SourceMask = 1 << 5;
static constexpr int DestinationMask = 1 << 6;
uint8_t action = uint8_t(Action::None); // Requires 7 bits at present; sizeof(Action) + the two flags above.
static constexpr uint16_t NoBusProgram = std::numeric_limits<uint16_t>::max();
uint16_t bus_program = NoBusProgram; // Empirically requires 11 bits at present.
MicroOp(): action(uint8_t(Action::None)), bus_program(NoBusProgram) {}
MicroOp(uint8_t action) : action(action), bus_program(NoBusProgram) {}
MicroOp(uint8_t action, uint16_t bus_program) : action(action), bus_program(bus_program) {}
MicroOp(Action action) : MicroOp(uint8_t(action)) {}
MicroOp(Action action, uint16_t bus_program) : MicroOp(uint8_t(action), bus_program) {}
forceinline bool is_terminal() const {
return bus_program == NoBusProgram;
}
};
/*!
A program represents the implementation of a particular opcode, as a sequence
of micro-ops and, separately, the operation to perform plus whatever other
fields the operation requires.
Some of the fields are slightly convoluted in how they identify the information
they reference; this is done to keep this struct as small as possible due to
concerns about cache size.
On the 64-bit Intel processor this emulator was developed on, the struct below
adds up to 8 bytes; four for the initial uint32_t and then one each for the
remaining fields, with no additional padding being inserted by the compiler.
*/
struct Program {
Program() {
// Initialisers for bitfields aren't available until C++20. So, yuck, do it manually.
requires_supervisor = 0;
source = 0;
dest = 0;
destination_offset = 0;
source_offset = 0;
}
static constexpr uint32_t NoSuchProgram = std::numeric_limits<uint32_t>::max();
/// The offset into the all_micro_ops_ at which micro-ops for this instruction begin,
/// or std::numeric_limits<uint32_t>::max() if this is an invalid Program.
uint32_t micro_operations = NoSuchProgram;
/// The overarching operation applied by this program when the moment comes.
Operation operation;
/// The number of bytes after the beginning of an instance of ProcessorStorage that the RegisterPair32 containing
/// a source value for this operation lies at.
uint8_t source_offset: 7;
/// The number of bytes after the beginning of an instance of ProcessorStorage that the RegisterPair32 containing
/// a destination value for this operation lies at.
uint8_t destination_offset: 7;
/// Set if this program requires supervisor mode.
bool requires_supervisor: 1;
/// The source address register (for pre-decrement and post-increment actions).
uint8_t source: 3;
/// Destination address register.
uint8_t dest: 3;
void set_source_address([[maybe_unused]] ProcessorStorage &storage, int index) {
source = uint8_t(index);
assert(int(source) == index);
}
void set_destination_address([[maybe_unused]] ProcessorStorage &storage, int index) {
dest = uint8_t(index);
assert(int(dest) == index);
}
void set_requires_supervisor(bool req) {
requires_supervisor = req;
assert(requires_supervisor == req);
}
void set_source(ProcessorStorage &storage, RegisterPair32 *target) {
source_offset = decltype(source_offset)(reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage));
// Test that destination_offset could be stored fully within the integer size provided for source_offset.
assert(source_offset == (reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage)));
}
void set_destination(ProcessorStorage &storage, RegisterPair32 *target) {
destination_offset = decltype(destination_offset)(reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage));
// Test that destination_offset could be stored fully within the integer size provided for destination_offset.
assert(destination_offset == (reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage)));
}
void set_source(ProcessorStorage &storage, int mode, int reg) {
set_source_address(storage, reg);
switch(mode) {
case 0: set_source(storage, &storage.data_[reg]); break;
case 1: set_source(storage, &storage.address_[reg]); break;
default: set_source(storage, &storage.source_bus_data_); break;
}
}
void set_destination(ProcessorStorage &storage, int mode, int reg) {
set_destination_address(storage, reg);
switch(mode) {
case 0: set_destination(storage, &storage.data_[reg]); break;
case 1: set_destination(storage, &storage.address_[reg]); break;
default: set_destination(storage, &storage.destination_bus_data_); break;
}
}
};
// Storage for all the sequences of bus steps and micro-ops used throughout
// the 68000.
std::vector<BusStep> all_bus_steps_;
std::vector<MicroOp> all_micro_ops_;
// A lookup table from instructions to implementations.
Program instructions[65536];
// Special steps and programs for exception handlers.
BusStep *reset_bus_steps_;
MicroOp *long_exception_micro_ops_; // i.e. those that leave 14 bytes on the stack — bus error and address error.
MicroOp *short_exception_micro_ops_; // i.e. those that leave 6 bytes on the stack — everything else (other than interrupts).
MicroOp *interrupt_micro_ops_;
// Special micro-op sequences and storage for conditionals.
BusStep *branch_taken_bus_steps_;
BusStep *branch_byte_not_taken_bus_steps_;
BusStep *branch_word_not_taken_bus_steps_;
BusStep *bsr_bus_steps_;
uint32_t dbcc_false_address_;
BusStep *dbcc_condition_true_steps_;
BusStep *dbcc_condition_false_no_branch_steps_;
BusStep *dbcc_condition_false_branch_steps_;
BusStep *movem_read_steps_;
BusStep *movem_write_steps_;
// These two are dynamically modified depending on the particular
// TRAP and bus error.
BusStep *trap_steps_;
BusStep *bus_error_steps_;
// Current bus step pointer, and outer program pointer.
const Program *active_program_ = nullptr;
const MicroOp *active_micro_op_ = nullptr;
const BusStep *active_step_ = nullptr;
RegisterPair16 decoded_instruction_ = 0;
uint16_t next_word_ = 0;
/// Copies address_[7] to the proper stack pointer based on current mode.
void write_back_stack_pointer();
/// Sets or clears the supervisor flag, ensuring the stack pointer is properly updated.
void set_is_supervisor(bool);
// Transient storage for MOVEM, TRAP and others.
RegisterPair16 throwaway_value_;
uint32_t movem_final_address_;
uint32_t precomputed_addresses_[65]; // This is a big chunk of rarely-used storage. It's placed last deliberately.
/*!
Evaluates the conditional described by @c code and returns @c true or @c false to
indicate the result of that evaluation.
*/
forceinline bool evaluate_condition(uint8_t code) {
switch(code & 0xf) {
default:
case 0x00: return true; // true
case 0x01: return false; // false
case 0x02: return zero_result_ && !carry_flag_; // high
case 0x03: return !zero_result_ || carry_flag_; // low or same
case 0x04: return !carry_flag_; // carry clear
case 0x05: return carry_flag_; // carry set
case 0x06: return zero_result_; // not equal
case 0x07: return !zero_result_; // equal
case 0x08: return !overflow_flag_; // overflow clear
case 0x09: return overflow_flag_; // overflow set
case 0x0a: return !negative_flag_; // positive
case 0x0b: return negative_flag_; // negative
case 0x0c: // greater than or equal
return (negative_flag_ && overflow_flag_) || (!negative_flag_ && !overflow_flag_);
case 0x0d: // less than
return (negative_flag_ && !overflow_flag_) || (!negative_flag_ && overflow_flag_);
case 0x0e: // greater than
return zero_result_ && ((negative_flag_ && overflow_flag_) || (!negative_flag_ && !overflow_flag_));
case 0x0f: // less than or equal
return !zero_result_ || (negative_flag_ && !overflow_flag_) || (!negative_flag_ && overflow_flag_);
}
}
/*!
Fills in the appropriate addresses and values to complete the TRAP steps those
representing a short-form exception and mutates the status register as if one
were beginning.
*/
forceinline void populate_trap_steps(uint32_t vector, uint16_t status) {
// Fill in the status word value.
destination_bus_data_.full = status;
// Switch to supervisor mode, disable the trace bit.
set_is_supervisor(true);
trace_flag_ = last_trace_flag_ = 0;
// Pick a vector.
effective_address_[0].full = vector << 2;
// Schedule the proper stack activity.
precomputed_addresses_[0] = address_[7].full - 2; // PC.l
precomputed_addresses_[1] = address_[7].full - 6; // status word (in destination_bus_data_)
precomputed_addresses_[2] = address_[7].full - 4; // PC.h
address_[7].full -= 6;
// Set the default timing.
trap_steps_->microcycle.length = HalfCycles(8);
}
forceinline void populate_bus_error_steps(uint32_t vector, uint16_t status, uint16_t bus_status, RegisterPair32 faulting_address) {
// Fill in the status word value.
destination_bus_data_.halves.low.full = status;
destination_bus_data_.halves.high.full = bus_status;
effective_address_[1] = faulting_address;
// Switch to supervisor mode, disable the trace bit.
set_is_supervisor(true);
trace_flag_ = last_trace_flag_ = 0;
// Pick a vector.
effective_address_[0].full = vector << 2;
// Schedule the proper stack activity.
precomputed_addresses_[0] = address_[7].full - 2; // PC.l
precomputed_addresses_[1] = address_[7].full - 6; // status word
precomputed_addresses_[2] = address_[7].full - 4; // PC.h
precomputed_addresses_[3] = address_[7].full - 8; // current instruction
precomputed_addresses_[4] = address_[7].full - 10; // fault address.l
precomputed_addresses_[5] = address_[7].full - 14; // bus cycle status word
precomputed_addresses_[6] = address_[7].full - 12; // fault address.h
address_[7].full -= 14;
}
inline uint16_t get_status() const;
inline void set_status(uint16_t);
private:
friend struct ProcessorStorageConstructor;
friend class ProcessorStorageTests;
friend struct State;
};
#endif /* MC68000Storage_h */

View File

@ -1,340 +0,0 @@
//
// State.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "State.hpp"
#include <cassert>
using namespace CPU::MC68000;
State::State(const ProcessorBase &src): State() {
// Registers.
for(int c = 0; c < 7; ++c) {
registers.address[c] = src.address_[c].full;
registers.data[c] = src.data_[c].full;
}
registers.data[7] = src.data_[7].full;
registers.user_stack_pointer = src.is_supervisor_ ? src.stack_pointers_[0].full : src.address_[7].full;
registers.supervisor_stack_pointer = src.is_supervisor_ ? src.address_[7].full : src.stack_pointers_[1].full;
registers.status = src.get_status();
registers.program_counter = src.program_counter_.full;
registers.prefetch = src.prefetch_queue_.full;
registers.instruction = src.decoded_instruction_.full;
// Inputs.
inputs.bus_interrupt_level = uint8_t(src.bus_interrupt_level_);
inputs.dtack = src.dtack_;
inputs.is_peripheral_address = src.is_peripheral_address_;
inputs.bus_error = src.bus_error_;
inputs.bus_request = src.bus_request_;
inputs.bus_grant = false; // TODO (within the 68000).
inputs.halt = src.halt_;
// Execution state.
execution_state.e_clock_phase = src.e_clock_phase_.as<uint8_t>();
execution_state.effective_address[0] = src.effective_address_[0].full;
execution_state.effective_address[1] = src.effective_address_[1].full;
execution_state.source_data = src.source_bus_data_.full;
execution_state.destination_data = src.destination_bus_data_.full;
execution_state.last_trace_flag = src.last_trace_flag_;
execution_state.next_word = src.next_word_;
execution_state.dbcc_false_address = src.dbcc_false_address_;
execution_state.is_starting_interrupt = src.is_starting_interrupt_;
execution_state.pending_interrupt_level = uint8_t(src.pending_interrupt_level_);
execution_state.accepted_interrupt_level = uint8_t(src.accepted_interrupt_level_);
execution_state.movem_final_address = src.movem_final_address_;
static_assert(sizeof(execution_state.source_addresses) == sizeof(src.precomputed_addresses_));
memcpy(&execution_state.source_addresses, &src.precomputed_addresses_, sizeof(src.precomputed_addresses_));
// This is collapsed to a Boolean; if there is an active program then it's the
// one implied by the current instruction.
execution_state.active_program = src.active_program_;
// Slightly dodgy assumption here: the Phase enum will always exactly track
// the 68000's ExecutionState enum.
execution_state.phase = ExecutionState::Phase(src.execution_state_);
auto contained_by = [](const auto *source, const auto *reference) -> bool {
while(true) {
if(source == reference) return true;
if(source->is_terminal()) return false;
++source;
}
};
// Store enough information to relocate the MicroOp.
const ProcessorBase::MicroOp *micro_op_base = nullptr;
if(src.active_program_) {
micro_op_base = &src.all_micro_ops_[src.instructions[src.decoded_instruction_.full].micro_operations];
assert(contained_by(micro_op_base, src.active_micro_op_));
execution_state.micro_op_source = ExecutionState::MicroOpSource::ActiveProgram;
} else {
if(contained_by(src.long_exception_micro_ops_, src.active_micro_op_)) {
execution_state.micro_op_source = ExecutionState::MicroOpSource::LongException;
micro_op_base = src.long_exception_micro_ops_;
} else if(contained_by(src.short_exception_micro_ops_, src.active_micro_op_)) {
execution_state.micro_op_source = ExecutionState::MicroOpSource::ShortException;
micro_op_base = src.short_exception_micro_ops_;
} else if(contained_by(src.interrupt_micro_ops_, src.active_micro_op_)) {
execution_state.micro_op_source = ExecutionState::MicroOpSource::Interrupt;
micro_op_base = src.interrupt_micro_ops_;
} else {
assert(false);
}
}
execution_state.micro_op = uint8_t(src.active_micro_op_ - micro_op_base);
// Encode the BusStep.
struct BusStepOption {
const ProcessorBase::BusStep *const base;
const ExecutionState::BusStepSource source = ExecutionState::BusStepSource::FollowMicroOp;
};
BusStepOption bus_step_options[] = {
{
src.reset_bus_steps_,
ExecutionState::BusStepSource::Reset
},
{
src.branch_taken_bus_steps_,
ExecutionState::BusStepSource::BranchTaken
},
{
src.branch_byte_not_taken_bus_steps_,
ExecutionState::BusStepSource::BranchByteNotTaken
},
{
src.branch_word_not_taken_bus_steps_,
ExecutionState::BusStepSource::BranchWordNotTaken
},
{
src.bsr_bus_steps_,
ExecutionState::BusStepSource::BSR
},
{
src.dbcc_condition_true_steps_,
ExecutionState::BusStepSource::DBccConditionTrue
},
{
src.dbcc_condition_false_no_branch_steps_,
ExecutionState::BusStepSource::DBccConditionFalseNoBranch
},
{
src.dbcc_condition_false_branch_steps_,
ExecutionState::BusStepSource::DBccConditionFalseBranch
},
{
src.movem_read_steps_,
ExecutionState::BusStepSource::MovemRead
},
{
src.movem_write_steps_,
ExecutionState::BusStepSource::MovemWrite
},
{
src.trap_steps_,
ExecutionState::BusStepSource::Trap
},
{
src.bus_error_steps_,
ExecutionState::BusStepSource::BusError
},
{
&src.all_bus_steps_[src.active_micro_op_->bus_program],
ExecutionState::BusStepSource::FollowMicroOp
},
{nullptr}
};
const BusStepOption *bus_step_option = bus_step_options;
const ProcessorBase::BusStep *bus_step_base = nullptr;
while(bus_step_option->base) {
if(contained_by(bus_step_option->base, src.active_step_)) {
bus_step_base = bus_step_option->base;
execution_state.bus_step_source = bus_step_option->source;
break;
}
++bus_step_option;
}
assert(bus_step_base);
execution_state.bus_step = uint8_t(src.active_step_ - bus_step_base);
}
void State::apply(ProcessorBase &target) {
// Registers.
for(int c = 0; c < 7; ++c) {
target.address_[c].full = registers.address[c];
target.data_[c].full = registers.data[c];
}
target.data_[7].full = registers.data[7];
target.stack_pointers_[0] = registers.user_stack_pointer;
target.stack_pointers_[1] = registers.supervisor_stack_pointer;
target.address_[7] = target.stack_pointers_[(registers.status & 0x2000) >> 13];
target.set_status(registers.status);
target.program_counter_.full = registers.program_counter;
target.prefetch_queue_.full = registers.prefetch;
target.decoded_instruction_.full = registers.instruction;
// Inputs.
target.bus_interrupt_level_ = inputs.bus_interrupt_level;
target.dtack_ = inputs.dtack;
target.is_peripheral_address_ = inputs.is_peripheral_address;
target.bus_error_ = inputs.bus_error;
target.bus_request_ = inputs.bus_request;
// TODO: bus_grant.
target.halt_ = inputs.halt;
// Execution state.
target.e_clock_phase_ = HalfCycles(execution_state.e_clock_phase);
target.effective_address_[0].full = execution_state.effective_address[0];
target.effective_address_[1].full = execution_state.effective_address[1];
target.source_bus_data_.full = execution_state.source_data;
target.destination_bus_data_.full = execution_state.destination_data;
target.last_trace_flag_ = execution_state.last_trace_flag;
target.next_word_ = execution_state.next_word;
target.dbcc_false_address_ = execution_state.dbcc_false_address;
target.is_starting_interrupt_ = execution_state.is_starting_interrupt;
target.pending_interrupt_level_ = execution_state.pending_interrupt_level;
target.accepted_interrupt_level_ = execution_state.accepted_interrupt_level;
target.movem_final_address_ = execution_state.movem_final_address;
static_assert(sizeof(execution_state.source_addresses) == sizeof(target.precomputed_addresses_));
memcpy(&target.precomputed_addresses_, &execution_state.source_addresses, sizeof(target.precomputed_addresses_));
// See above; this flag indicates whether to populate the field.
target.active_program_ =
execution_state.active_program ?
&target.instructions[target.decoded_instruction_.full] : nullptr;
// Dodgy assumption duplicated here from above.
target.execution_state_ = CPU::MC68000::ProcessorStorage::ExecutionState(execution_state.phase);
// Decode the MicroOp.
switch(execution_state.micro_op_source) {
case ExecutionState::MicroOpSource::ActiveProgram:
target.active_micro_op_ = &target.all_micro_ops_[target.active_program_->micro_operations];
break;
case ExecutionState::MicroOpSource::LongException:
target.active_micro_op_ = target.long_exception_micro_ops_;
break;
case ExecutionState::MicroOpSource::ShortException:
target.active_micro_op_ = target.short_exception_micro_ops_;
break;
case ExecutionState::MicroOpSource::Interrupt:
target.active_micro_op_ = target.interrupt_micro_ops_;
break;
}
target.active_micro_op_ += execution_state.micro_op;
// Decode the BusStep.
switch(execution_state.bus_step_source) {
case ExecutionState::BusStepSource::Reset:
target.active_step_ = target.reset_bus_steps_;
break;
case ExecutionState::BusStepSource::BranchTaken:
target.active_step_ = target.branch_taken_bus_steps_;
break;
case ExecutionState::BusStepSource::BranchByteNotTaken:
target.active_step_ = target.branch_byte_not_taken_bus_steps_;
break;
case ExecutionState::BusStepSource::BranchWordNotTaken:
target.active_step_ = target.branch_word_not_taken_bus_steps_;
break;
case ExecutionState::BusStepSource::BSR:
target.active_step_ = target.bsr_bus_steps_;
break;
case ExecutionState::BusStepSource::DBccConditionTrue:
target.active_step_ = target.dbcc_condition_true_steps_;
break;
case ExecutionState::BusStepSource::DBccConditionFalseNoBranch:
target.active_step_ = target.dbcc_condition_false_no_branch_steps_;
break;
case ExecutionState::BusStepSource::DBccConditionFalseBranch:
target.active_step_ = target.dbcc_condition_false_branch_steps_;
break;
case ExecutionState::BusStepSource::MovemRead:
target.active_step_ = target.movem_read_steps_;
break;
case ExecutionState::BusStepSource::MovemWrite:
target.active_step_ = target.movem_write_steps_;
break;
case ExecutionState::BusStepSource::Trap:
target.active_step_ = target.trap_steps_;
break;
case ExecutionState::BusStepSource::BusError:
target.active_step_ = target.bus_error_steps_;
break;
case ExecutionState::BusStepSource::FollowMicroOp:
target.active_step_ = &target.all_bus_steps_[target.active_micro_op_->bus_program];
break;
}
target.active_step_ += execution_state.bus_step;
}
// Boilerplate follows here, to establish 'reflection'.
State::State() {
if(needs_declare()) {
DeclareField(registers);
DeclareField(execution_state);
DeclareField(inputs);
}
}
State::Registers::Registers() {
if(needs_declare()) {
DeclareField(data);
DeclareField(address);
DeclareField(user_stack_pointer);
DeclareField(supervisor_stack_pointer);
DeclareField(status);
DeclareField(program_counter);
DeclareField(prefetch);
DeclareField(instruction);
}
}
State::Inputs::Inputs() {
if(needs_declare()) {
DeclareField(bus_interrupt_level);
DeclareField(dtack);
DeclareField(is_peripheral_address);
DeclareField(bus_error);
DeclareField(bus_request);
DeclareField(bus_grant);
DeclareField(halt);
}
}
State::ExecutionState::ExecutionState() {
if(needs_declare()) {
DeclareField(e_clock_phase);
DeclareField(effective_address);
DeclareField(source_data);
DeclareField(destination_data);
DeclareField(last_trace_flag);
DeclareField(next_word);
DeclareField(dbcc_false_address);
DeclareField(is_starting_interrupt);
DeclareField(pending_interrupt_level);
DeclareField(accepted_interrupt_level);
DeclareField(active_program);
DeclareField(movem_final_address);
DeclareField(source_addresses);
AnnounceEnum(Phase);
DeclareField(phase);
AnnounceEnum(MicroOpSource);
DeclareField(micro_op_source);
DeclareField(micro_op);
AnnounceEnum(BusStepSource);
DeclareField(bus_step_source);
DeclareField(bus_step);
}
}

View File

@ -1,137 +0,0 @@
//
// State.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef MC68000_State_hpp
#define MC68000_State_hpp
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../68000.hpp"
namespace CPU {
namespace MC68000 {
/*!
Provides a means for capturing or restoring complete 68000 state.
This is an optional adjunct to the 68000 class. If you want to take the rest of the 68000
implementation but don't want any of the overhead of my sort-of half-reflection as
encapsulated in Reflection/[Enum/Struct].hpp just don't use this class.
*/
struct State: public Reflection::StructImpl<State> {
/*!
Provides the current state of the well-known, published internal registers.
*/
struct Registers: public Reflection::StructImpl<Registers> {
// Official registers.
uint32_t data[8], address[7];
uint32_t user_stack_pointer;
uint32_t supervisor_stack_pointer;
uint16_t status;
uint32_t program_counter;
// The 68000's prefetch queue.
uint32_t prefetch;
uint16_t instruction;
Registers();
} registers;
/*!
Provides the current state of the processor's various input lines that aren't
related to an access cycle.
*/
struct Inputs: public Reflection::StructImpl<Inputs> {
uint8_t bus_interrupt_level;
bool dtack;
bool is_peripheral_address;
bool bus_error;
bool bus_request;
bool bus_grant;
bool halt;
Inputs();
} inputs;
/*!
Contains internal state used by this particular implementation of a 68000. Most of it
does not necessarily correlate with anything in a real 68000, and some of it very
obviously doesn't.
*/
struct ExecutionState: public Reflection::StructImpl<ExecutionState> {
uint8_t e_clock_phase;
uint32_t effective_address[2];
uint32_t source_data;
uint32_t destination_data;
bool last_trace_flag;
uint16_t next_word;
uint32_t dbcc_false_address;
bool is_starting_interrupt;
uint8_t pending_interrupt_level;
uint8_t accepted_interrupt_level;
// This is a reflective do-over of the ExecutionState enum within
// MC68000Storage; I've yet to decide how happy I am with that
// as an approach.
ReflectableEnum(Phase,
Executing,
WaitingForDTack,
Stopped,
Halted,
WillBeginInterrupt
);
Phase phase;
bool active_program;
uint32_t movem_final_address;
uint32_t source_addresses[65];
ReflectableEnum(MicroOpSource,
ActiveProgram,
LongException,
ShortException,
Interrupt
);
MicroOpSource micro_op_source;
uint8_t micro_op;
ReflectableEnum(BusStepSource,
FollowMicroOp,
BusError,
Trap,
Reset,
BranchTaken,
BranchByteNotTaken,
BranchWordNotTaken,
BSR,
DBccConditionTrue,
DBccConditionFalseNoBranch,
DBccConditionFalseBranch,
MovemRead,
MovemWrite,
);
BusStepSource bus_step_source;
uint8_t bus_step;
ExecutionState();
} execution_state;
/// Default constructor; makes no guarantees as to field values beyond those given above.
State();
/// Instantiates a new State based on the processor @c src.
State(const ProcessorBase &src);
/// Applies this state to @c target.
void apply(ProcessorBase &target);
};
}
}
#endif /* MC68000_State_hpp */