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:
parent
2b56b7be0d
commit
e56db3c4e5
@ -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"
|
||||
|
||||
|
@ -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 */,
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "68000.hpp"
|
||||
#include "68000Mk2.hpp"
|
||||
#include "Comparative68000.hpp"
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
||||
|
@ -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
@ -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 */
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 */
|
Loading…
x
Reference in New Issue
Block a user