mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #621 from TomHarte/Mac128k
Adds preliminary emulation of the 512ke Macintosh.
This commit is contained in:
commit
56555a4d99
@ -59,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
|
||||
}
|
||||
}
|
||||
|
||||
MouseMachine::Machine *MultiMachine::mouse_machine() {
|
||||
// TODO.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Configurable::Device *MultiMachine::configurable_device() {
|
||||
if(has_picked_) {
|
||||
return machines_.front()->configurable_device();
|
||||
|
@ -54,6 +54,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
|
||||
Configurable::Device *configurable_device() override;
|
||||
CRTMachine::Machine *crt_machine() override;
|
||||
JoystickMachine::Machine *joystick_machine() override;
|
||||
MouseMachine::Machine *mouse_machine() override;
|
||||
KeyboardMachine::Machine *keyboard_machine() override;
|
||||
MediaTarget::Machine *media_target() override;
|
||||
void *raw_pointer() override;
|
||||
|
@ -17,6 +17,7 @@ enum class Machine {
|
||||
Atari2600,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Macintosh,
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
|
@ -290,6 +290,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
|
||||
target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe;
|
||||
|
||||
// Blindly accept disks for now.
|
||||
// TODO: how to spot an MSX disk?
|
||||
target->media.disks = media.disks;
|
||||
target->has_disk_drive = !media.disks.empty();
|
||||
|
||||
|
26
Analyser/Static/Macintosh/StaticAnalyser.cpp
Normal file
26
Analyser/Static/Macintosh/StaticAnalyser.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// If there is at least one disk, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
auto *target = new Target;
|
||||
target->machine = Analyser::Machine::Macintosh;
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
27
Analyser/Static/Macintosh/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Macintosh/StaticAnalyser.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
|
31
Analyser/Static/Macintosh/Target.hpp
Normal file
31
Analyser/Static/Macintosh/Target.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Macintosh_Target_h
|
||||
#define Analyser_Static_Macintosh_Target_h
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
Mac128k,
|
||||
Mac512k,
|
||||
Mac512ke,
|
||||
MacPlus
|
||||
};
|
||||
|
||||
Model model = Model::Mac512ke;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_Target_h */
|
@ -21,6 +21,7 @@
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
#include "Sega/StaticAnalyser.hpp"
|
||||
@ -35,12 +36,14 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/PlusTooBIN.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
@ -85,34 +88,38 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
TryInsert(list, class, platforms) \
|
||||
}
|
||||
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
// Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||
Format("bin", result.disks, Disk::DiskImageHolder<Storage::Disk::PlusTooBIN>, TargetPlatform::Macintosh) // BIN (PlusToo disk image)
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
|
||||
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
|
||||
|
||||
// PRG
|
||||
if(extension == "prg") {
|
||||
@ -129,16 +136,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format( "rom",
|
||||
result.cartridges,
|
||||
Cartridge::BinaryDump,
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
|
||||
|
||||
#undef Format
|
||||
#undef Insert
|
||||
@ -173,9 +180,10 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
|
||||
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
|
||||
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
|
||||
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
|
||||
if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh);
|
||||
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
|
||||
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
|
||||
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
|
||||
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
|
||||
#undef Append
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
#ifndef ClockReceiver_hpp
|
||||
#define ClockReceiver_hpp
|
||||
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
/*
|
||||
Informal pattern for all classes that run from a clock cycle:
|
||||
|
||||
@ -52,79 +54,92 @@
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
constexpr WrappedInt(int l) : length_(l) {}
|
||||
constexpr WrappedInt() : length_(0) {}
|
||||
forceinline constexpr WrappedInt(int l) : length_(l) {}
|
||||
forceinline constexpr WrappedInt() : length_(0) {}
|
||||
|
||||
T &operator =(const T &rhs) {
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
T &operator +=(const T &rhs) {
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator -=(const T &rhs) {
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator ++() {
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator ++(int) {
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator --() {
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator --(int) {
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator %=(const T &rhs) {
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
T &operator &=(const T &rhs) {
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
|
||||
constexpr T operator -() const { return T(- length_); }
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
constexpr bool operator !() const { return !length_; }
|
||||
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
constexpr int as_int() const { return length_; }
|
||||
forceinline constexpr int as_int() const { return length_; }
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
T divide(const T &divisor) {
|
||||
forceinline T divide(const T &divisor) {
|
||||
T result(length_ / divisor.length_);
|
||||
length_ %= divisor.length_;
|
||||
return result;
|
||||
@ -134,7 +149,7 @@ template <class T> class WrappedInt {
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
T flush() {
|
||||
forceinline T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@ -150,34 +165,34 @@ template <class T> class WrappedInt {
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
forceinline constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
forceinline constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
forceinline constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
forceinline constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
constexpr Cycles cycles() const {
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
Cycles flush_cycles() {
|
||||
forceinline Cycles flush_cycles() {
|
||||
Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
|
||||
HalfCycles flush() {
|
||||
forceinline HalfCycles flush() {
|
||||
HalfCycles result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@ -187,9 +202,9 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
Cycles divide_cycles(const Cycles &divisor) {
|
||||
HalfCycles half_divisor = HalfCycles(divisor);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
@ -203,7 +218,7 @@ template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
}
|
||||
|
@ -47,6 +47,12 @@ class PortHandler {
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status(bool status) {}
|
||||
|
||||
/// Provides a measure of time elapsed between other calls.
|
||||
void run_for(HalfCycles duration) {}
|
||||
|
||||
/// Receives passed-on flush() calls from the 6522.
|
||||
void flush() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -71,26 +77,6 @@ class IRQDelegatePortHandler: public PortHandler {
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class MOS6522Base: public MOS6522Storage {
|
||||
public:
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
|
||||
/// Runs for a specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Runs for a specified number of cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
inline void do_phase1();
|
||||
inline void do_phase2();
|
||||
virtual void reevaluate_interrupts() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
|
||||
|
||||
@ -102,7 +88,7 @@ class MOS6522Base: public MOS6522Storage {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522: public MOS6522Base {
|
||||
template <class T> class MOS6522: public MOS6522Storage {
|
||||
public:
|
||||
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(const MOS6522 &) = delete;
|
||||
@ -116,11 +102,39 @@ template <class T> class MOS6522: public MOS6522Base {
|
||||
/*! @returns the bus handler. */
|
||||
T &bus_handler();
|
||||
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
|
||||
/// Runs for a specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Runs for a specified number of cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
/// Updates the port handler to the current time and then requests that it flush.
|
||||
void flush();
|
||||
|
||||
private:
|
||||
void do_phase1();
|
||||
void do_phase2();
|
||||
void shift_in();
|
||||
void shift_out();
|
||||
|
||||
T &bus_handler_;
|
||||
HalfCycles time_since_bus_handler_call_;
|
||||
|
||||
void access(int address);
|
||||
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
|
||||
inline void reevaluate_interrupts();
|
||||
|
||||
/// Sets the current intended output value for the port and line;
|
||||
/// if this affects the visible output, it will be passed to the handler.
|
||||
void set_control_line_output(Port port, Line line, LineState value);
|
||||
void evaluate_cb2_output();
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
//
|
||||
// 6522Base.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "../6522.hpp"
|
||||
|
||||
using namespace MOS::MOS6522;
|
||||
|
||||
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if( value != control_inputs_[port].line_one &&
|
||||
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO: output modes, but probably elsewhere?
|
||||
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].line_two = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase2() {
|
||||
registers_.last_timer[0] = registers_.timer[0];
|
||||
registers_.last_timer[1] = registers_.timer[1];
|
||||
|
||||
if(registers_.timer_needs_reload) {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MOS6522Base::do_phase1() {
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||
timer_is_running_[1] = false;
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
registers_.timer_needs_reload = true;
|
||||
else
|
||||
timer_is_running_[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
void MOS6522Base::run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
|
||||
if(is_phase2_) {
|
||||
do_phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
do_phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
void MOS6522Base::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
bool MOS6522Base::get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
@ -11,29 +11,58 @@
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
template <typename T> void MOS6522<T>::access(int address) {
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
// In both handshake and pulse modes, CB2 goes low on any read or write of Port B.
|
||||
if(handshake_modes_[1] != HandshakeMode::None) {
|
||||
set_control_line_output(Port::B, Line::Two, LineState::Off);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
|
||||
if(handshake_modes_[0] != HandshakeMode::None) {
|
||||
set_control_line_output(Port::A, Line::Two, LineState::Off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
access(address);
|
||||
switch(address) {
|
||||
case 0x0: // Write Port B.
|
||||
// Store locally and communicate outwards.
|
||||
registers_.output[1] = value;
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
case 0x1: // Write Port A.
|
||||
registers_.output[0] = value;
|
||||
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);
|
||||
|
||||
if(handshake_modes_[1] != HandshakeMode::None) {
|
||||
set_control_line_output(Port::A, Line::Two, LineState::Off);
|
||||
}
|
||||
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
case 0x2: // Port B direction.
|
||||
registers_.data_direction[1] = value;
|
||||
break;
|
||||
case 0x3:
|
||||
case 0x3: // Port A direction.
|
||||
registers_.data_direction[0] = value;
|
||||
break;
|
||||
|
||||
@ -59,31 +88,57 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
|
||||
break;
|
||||
|
||||
// Shift
|
||||
case 0xa: registers_.shift = value; break;
|
||||
case 0xa:
|
||||
registers_.shift = value;
|
||||
shift_bits_remaining_ = 8;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
|
||||
// Control
|
||||
case 0xb:
|
||||
registers_.auxiliary_control = value;
|
||||
evaluate_cb2_output();
|
||||
break;
|
||||
case 0xc:
|
||||
case 0xc: {
|
||||
// const auto old_peripheral_control = registers_.peripheral_control;
|
||||
registers_.peripheral_control = value;
|
||||
|
||||
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
|
||||
if(value & 0x08) {
|
||||
switch(value & 0x0e) {
|
||||
default: LOG("Unimplemented control line mode " << int((value >> 1)&7)); break;
|
||||
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
|
||||
int shift = 0;
|
||||
for(int port = 0; port < 2; ++port) {
|
||||
handshake_modes_[port] = HandshakeMode::None;
|
||||
switch((value >> shift) & 0x0e) {
|
||||
default: break;
|
||||
|
||||
case 0x00: // Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register.
|
||||
case 0x02: // Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically.
|
||||
case 0x04: // Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register.
|
||||
case 0x06: // Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically.
|
||||
set_control_line_output(Port(port), Line::Two, LineState::Input);
|
||||
break;
|
||||
|
||||
case 0x08: // Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1.
|
||||
handshake_modes_[port] = HandshakeMode::Handshake;
|
||||
set_control_line_output(Port(port), Line::Two, LineState::Off); // At a guess.
|
||||
break;
|
||||
|
||||
case 0x0a: // Pulse output: Cx2 is low for one cycle following a read or write of Port x.
|
||||
handshake_modes_[port] = HandshakeMode::Pulse;
|
||||
set_control_line_output(Port(port), Line::Two, LineState::On);
|
||||
break;
|
||||
|
||||
case 0x0c: // Manual output: Cx2 low.
|
||||
set_control_line_output(Port(port), Line::Two, LineState::Off);
|
||||
break;
|
||||
|
||||
case 0x0e: // Manual output: Cx2 high.
|
||||
set_control_line_output(Port(port), Line::Two, LineState::On);
|
||||
break;
|
||||
}
|
||||
|
||||
shift += 4;
|
||||
}
|
||||
if(value & 0x80) {
|
||||
switch(value & 0xe0) {
|
||||
default: LOG("Unimplemented control line mode " << int((value >> 5)&7)); break;
|
||||
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
@ -102,12 +157,13 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
address &= 0xf;
|
||||
access(address);
|
||||
switch(address) {
|
||||
case 0x0:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
@ -132,7 +188,11 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
return registers_.timer[1] & 0x00ff;
|
||||
case 0x9: return registers_.timer[1] >> 8;
|
||||
|
||||
case 0xa: return registers_.shift;
|
||||
case 0xa:
|
||||
shift_bits_remaining_ = 8;
|
||||
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
return registers_.shift;
|
||||
|
||||
case 0xb: return registers_.auxiliary_control;
|
||||
case 0xc: return registers_.peripheral_control;
|
||||
@ -145,7 +205,8 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
|
||||
}
|
||||
|
||||
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
|
||||
uint8_t input = bus_handler_.get_port_input(port);
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
const uint8_t input = bus_handler_.get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
}
|
||||
|
||||
@ -158,9 +219,238 @@ template <typename T> void MOS6522<T>::reevaluate_interrupts() {
|
||||
bool new_interrupt_status = get_interrupt_line();
|
||||
if(new_interrupt_status != last_posted_interrupt_status_) {
|
||||
last_posted_interrupt_status_ = new_interrupt_status;
|
||||
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
bus_handler_.set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) {
|
||||
switch(line) {
|
||||
case Line::One:
|
||||
if(value != control_inputs_[port].lines[line]) {
|
||||
// In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2.
|
||||
if(handshake_modes_[port] == HandshakeMode::Handshake) {
|
||||
set_control_line_output(port, Line::Two, LineState::On);
|
||||
}
|
||||
|
||||
// Set the proper transition interrupt bit if enabled.
|
||||
if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
// If this is a transition on CB1, consider updating the shift register.
|
||||
// TODO: and at least one full clock since the shift register was written?
|
||||
if(port == Port::B) {
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition.
|
||||
case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition.
|
||||
}
|
||||
}
|
||||
}
|
||||
control_inputs_[port].lines[line] = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
if( value != control_inputs_[port].lines[line] && // i.e. value has changed ...
|
||||
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
|
||||
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
|
||||
) {
|
||||
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
control_inputs_[port].lines[line] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::do_phase2() {
|
||||
++ time_since_bus_handler_call_;
|
||||
|
||||
registers_.last_timer[0] = registers_.timer[0];
|
||||
registers_.last_timer[1] = registers_.timer[1];
|
||||
|
||||
if(registers_.timer_needs_reload) {
|
||||
registers_.timer_needs_reload = false;
|
||||
registers_.timer[0] = registers_.timer_latch[0];
|
||||
} else {
|
||||
registers_.timer[0] --;
|
||||
}
|
||||
|
||||
registers_.timer[1] --;
|
||||
if(registers_.next_timer[0] >= 0) {
|
||||
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
|
||||
registers_.next_timer[0] = -1;
|
||||
}
|
||||
if(registers_.next_timer[1] >= 0) {
|
||||
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
|
||||
registers_.next_timer[1] = -1;
|
||||
}
|
||||
|
||||
// In pulse modes, CA2 and CB2 go high again on the next clock edge.
|
||||
if(handshake_modes_[1] == HandshakeMode::Pulse) {
|
||||
set_control_line_output(Port::B, Line::Two, LineState::On);
|
||||
}
|
||||
if(handshake_modes_[0] == HandshakeMode::Pulse) {
|
||||
set_control_line_output(Port::A, Line::Two, LineState::On);
|
||||
}
|
||||
|
||||
// If the shift register is shifting according to the input clock, do a shift.
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
case ShiftMode::InUnderPhase2: shift_in(); break;
|
||||
case ShiftMode::OutUnderPhase2: shift_out(); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::do_phase1() {
|
||||
++ time_since_bus_handler_call_;
|
||||
|
||||
// IRQ is raised on the half cycle after overflow
|
||||
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
|
||||
timer_is_running_[1] = false;
|
||||
|
||||
// If the shift register is shifting according to this timer, do a shift.
|
||||
// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
|
||||
switch(shift_mode()) {
|
||||
default: break;
|
||||
case ShiftMode::InUnderT2: shift_in(); break;
|
||||
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
|
||||
case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1.
|
||||
}
|
||||
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer2;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
|
||||
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
|
||||
registers_.interrupt_flags |= InterruptFlag::Timer1;
|
||||
reevaluate_interrupts();
|
||||
|
||||
// Determine whether to reload.
|
||||
if(registers_.auxiliary_control&0x40)
|
||||
registers_.timer_needs_reload = true;
|
||||
else
|
||||
timer_is_running_[0] = false;
|
||||
|
||||
// Determine whether to toggle PB7.
|
||||
if(registers_.auxiliary_control&0x80) {
|
||||
registers_.output[1] ^= 0x80;
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of half cycles. */
|
||||
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
|
||||
int number_of_half_cycles = half_cycles.as_int();
|
||||
if(!number_of_half_cycles) return;
|
||||
|
||||
if(is_phase2_) {
|
||||
do_phase2();
|
||||
number_of_half_cycles--;
|
||||
}
|
||||
|
||||
while(number_of_half_cycles >= 2) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
number_of_half_cycles -= 2;
|
||||
}
|
||||
|
||||
if(number_of_half_cycles) {
|
||||
do_phase1();
|
||||
is_phase2_ = true;
|
||||
} else {
|
||||
is_phase2_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::flush() {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
bus_handler_.flush();
|
||||
}
|
||||
|
||||
/*! Runs for a specified number of cycles. */
|
||||
template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
|
||||
int number_of_cycles = cycles.as_int();
|
||||
while(number_of_cycles--) {
|
||||
do_phase1();
|
||||
do_phase2();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
|
||||
template <typename T> bool MOS6522<T>::get_interrupt_line() {
|
||||
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
|
||||
return !!interrupt_status;
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
|
||||
// CB2 is a special case, being both the line the shift register can output to,
|
||||
// and one that can be used as an input or handshaking output according to the
|
||||
// peripheral control register.
|
||||
|
||||
// My guess: other CB2 functions work only if the shift register is disabled (?).
|
||||
if(shift_mode() != ShiftMode::Disabled) {
|
||||
// Shift register is enabled, one way or the other; but announce only output.
|
||||
if(is_shifting_out()) {
|
||||
// Output mode; set the level according to the current top of the shift register.
|
||||
bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80));
|
||||
} else {
|
||||
// Input mode.
|
||||
bus_handler_.set_control_line_output(Port::B, Line::Two, true);
|
||||
}
|
||||
} else {
|
||||
// Shift register is disabled.
|
||||
bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) {
|
||||
if(port == Port::B && line == Line::Two) {
|
||||
control_outputs_[port].lines[line] = value;
|
||||
evaluate_cb2_output();
|
||||
} else {
|
||||
// Do nothing if unchanged.
|
||||
if(value == control_outputs_[port].lines[line]) {
|
||||
return;
|
||||
}
|
||||
|
||||
control_outputs_[port].lines[line] = value;
|
||||
|
||||
if(value != LineState::Input) {
|
||||
bus_handler_.run_for(time_since_bus_handler_call_.flush());
|
||||
bus_handler_.set_control_line_output(port, line, value != LineState::Off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::shift_in() {
|
||||
registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0));
|
||||
--shift_bits_remaining_;
|
||||
if(!shift_bits_remaining_) {
|
||||
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void MOS6522<T>::shift_out() {
|
||||
// When shifting out, the shift register rotates rather than strictly shifts.
|
||||
// TODO: is that true for all modes?
|
||||
if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
|
||||
registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
|
||||
evaluate_cb2_output();
|
||||
|
||||
--shift_bits_remaining_;
|
||||
if(!shift_bits_remaining_) {
|
||||
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class MOS6522Storage {
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
|
||||
uint8_t output[2] = {0, 0};
|
||||
uint8_t input[2] = {0, 0};
|
||||
uint8_t data_direction[2] = {0, 0};
|
||||
@ -37,14 +37,27 @@ class MOS6522Storage {
|
||||
bool timer_needs_reload = false;
|
||||
} registers_;
|
||||
|
||||
// control state
|
||||
// Control state.
|
||||
struct {
|
||||
bool line_one = false;
|
||||
bool line_two = false;
|
||||
bool lines[2] = {false, false};
|
||||
} control_inputs_[2];
|
||||
|
||||
enum class LineState {
|
||||
On, Off, Input
|
||||
};
|
||||
struct {
|
||||
LineState lines[2] = {LineState::Input, LineState::Input};
|
||||
} control_outputs_[2];
|
||||
|
||||
enum class HandshakeMode {
|
||||
None,
|
||||
Handshake,
|
||||
Pulse
|
||||
} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
|
||||
|
||||
bool timer_is_running_[2] = {false, false};
|
||||
bool last_posted_interrupt_status_ = false;
|
||||
int shift_bits_remaining_ = 8;
|
||||
|
||||
enum InterruptFlag: uint8_t {
|
||||
CA2ActiveEdge = 1 << 0,
|
||||
@ -55,6 +68,23 @@ class MOS6522Storage {
|
||||
Timer2 = 1 << 5,
|
||||
Timer1 = 1 << 6,
|
||||
};
|
||||
|
||||
enum class ShiftMode {
|
||||
Disabled = 0,
|
||||
InUnderT2 = 1,
|
||||
InUnderPhase2 = 2,
|
||||
InUnderCB1 = 3,
|
||||
OutUnderT2FreeRunning = 4,
|
||||
OutUnderT2 = 5,
|
||||
OutUnderPhase2 = 6,
|
||||
OutUnderCB1 = 7
|
||||
};
|
||||
ShiftMode shift_mode() const {
|
||||
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
|
||||
}
|
||||
bool is_shifting_out() const {
|
||||
return registers_.auxiliary_control & 0x10;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
257
Components/8530/z8530.cpp
Normal file
257
Components/8530/z8530.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
//
|
||||
// 8530.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "z8530.hpp"
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Zilog::SCC;
|
||||
|
||||
void z8530::reset() {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
bool z8530::get_interrupt_line() {
|
||||
return
|
||||
(master_interrupt_control_ & 0x8) &&
|
||||
(
|
||||
channels_[0].get_interrupt_line() ||
|
||||
channels_[1].get_interrupt_line()
|
||||
);
|
||||
}
|
||||
|
||||
std::uint8_t z8530::read(int address) {
|
||||
if(address & 2) {
|
||||
// Read data register for channel
|
||||
return 0x00;
|
||||
} else {
|
||||
// Read control register for channel.
|
||||
uint8_t result = 0;
|
||||
|
||||
switch(pointer_) {
|
||||
default:
|
||||
result = channels_[address & 1].read(address & 2, pointer_);
|
||||
break;
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
LOG("[SCC] Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
// Modify the vector if permitted.
|
||||
// if(master_interrupt_control_ & 1) {
|
||||
for(int port = 0; port < 2; ++port) {
|
||||
// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix.
|
||||
if(channels_[port].get_interrupt_line()) {
|
||||
const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4);
|
||||
const uint8_t mask = uint8_t(~(7 << shift));
|
||||
result = uint8_t(
|
||||
(result & mask) |
|
||||
((1 | ((port == 1) ? 4 : 0)) << shift)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pointer_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void z8530::write(int address, std::uint8_t value) {
|
||||
if(address & 2) {
|
||||
// Write data register for channel.
|
||||
} else {
|
||||
// Write control register for channel.
|
||||
|
||||
// Most registers are per channel, but a couple are shared; sever
|
||||
// them here.
|
||||
switch(pointer_) {
|
||||
default:
|
||||
channels_[address & 1].write(address & 2, pointer_, value);
|
||||
break;
|
||||
|
||||
case 2: // Interrupt vector register; shared between both channels.
|
||||
interrupt_vector_ = value;
|
||||
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; also shared between both channels.
|
||||
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
|
||||
// The pointer number resets to 0 after every access, but if it is zero
|
||||
// then crib at least the next set of pointer bits (which, similarly, are shared
|
||||
// between the two channels).
|
||||
if(pointer_) {
|
||||
pointer_ = 0;
|
||||
} else {
|
||||
// The lowest three bits are the lowest three bits of the pointer.
|
||||
pointer_ = value & 7;
|
||||
|
||||
// If the command part of the byte is a 'point high', also set the
|
||||
// top bit of the pointer.
|
||||
if(((value >> 3)&7) == 1) {
|
||||
pointer_ |= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void z8530::set_dcd(int port, bool level) {
|
||||
channels_[port].set_dcd(level);
|
||||
}
|
||||
|
||||
// MARK: - Channel implementations
|
||||
|
||||
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
|
||||
// If this is a data read, just return it.
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
return 0x00;
|
||||
|
||||
case 0:
|
||||
return dcd_ ? 0x8 : 0x0;
|
||||
|
||||
case 0xf:
|
||||
return external_interrupt_status_;
|
||||
}
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
if(data) {
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
// Decode CRC reset instructions.
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
LOG("[SCC] TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode command code.
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
// LOG("[SCC] reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
LOG("[SCC] TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
LOG("[SCC] TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
LOG("[SCC] TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
LOG("[SCC] TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
|
||||
interrupt_mask_ = value;
|
||||
break;
|
||||
|
||||
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
|
||||
// Bits 0 and 1 select parity mode.
|
||||
if(!(value&1)) {
|
||||
parity_ = Parity::Off;
|
||||
} else {
|
||||
parity_ = (value&2) ? Parity::Even : Parity::Odd;
|
||||
}
|
||||
|
||||
// Bits 2 and 3 select stop bits.
|
||||
switch((value >> 2)&3) {
|
||||
default: stop_bits_ = StopBits::Synchronous; break;
|
||||
case 1: stop_bits_ = StopBits::OneBit; break;
|
||||
case 2: stop_bits_ = StopBits::OneAndAHalfBits; break;
|
||||
case 3: stop_bits_ = StopBits::TwoBits; break;
|
||||
}
|
||||
|
||||
// Bits 4 and 5 pick a sync mode.
|
||||
switch((value >> 4)&3) {
|
||||
default: sync_mode_ = Sync::Monosync; break;
|
||||
case 1: sync_mode_ = Sync::Bisync; break;
|
||||
case 2: sync_mode_ = Sync::SDLC; break;
|
||||
case 3: sync_mode_ = Sync::External; break;
|
||||
}
|
||||
|
||||
// Bits 6 and 7 select a clock rate multiplier, unless synchronous
|
||||
// mode is enabled (and this is ignored if sync mode is external).
|
||||
if(stop_bits_ == StopBits::Synchronous) {
|
||||
clock_rate_multiplier_ = 1;
|
||||
} else {
|
||||
switch((value >> 6)&3) {
|
||||
default: clock_rate_multiplier_ = 1; break;
|
||||
case 1: clock_rate_multiplier_ = 16; break;
|
||||
case 2: clock_rate_multiplier_ = 32; break;
|
||||
case 3: clock_rate_multiplier_ = 64; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xf: // Write register 15 — External/Status Interrupt Control.
|
||||
external_interrupt_mask_ = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void z8530::Channel::set_dcd(bool level) {
|
||||
if(dcd_ == level) return;
|
||||
dcd_ = level;
|
||||
|
||||
if(external_interrupt_mask_ & 0x8) {
|
||||
external_status_interrupt_ = true;
|
||||
external_interrupt_status_ |= 0x8;
|
||||
}
|
||||
}
|
||||
|
||||
bool z8530::Channel::get_interrupt_line() {
|
||||
return
|
||||
(interrupt_mask_ & 1) && external_status_interrupt_;
|
||||
// TODO: other potential causes of an interrupt.
|
||||
}
|
88
Components/8530/z8530.hpp
Normal file
88
Components/8530/z8530.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// z8530.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef z8530_hpp
|
||||
#define z8530_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Zilog {
|
||||
namespace SCC {
|
||||
|
||||
/*!
|
||||
Models the Zilog 8530 SCC, a serial adaptor.
|
||||
*/
|
||||
class z8530 {
|
||||
public:
|
||||
/*
|
||||
**Interface for emulated machine.**
|
||||
|
||||
Notes on addressing below:
|
||||
|
||||
There's no inherent ordering of the two 'address' lines,
|
||||
A/B and C/D, but the methods below assume:
|
||||
|
||||
A/B = A0
|
||||
C/D = A1
|
||||
*/
|
||||
std::uint8_t read(int address);
|
||||
void write(int address, std::uint8_t value);
|
||||
void reset();
|
||||
bool get_interrupt_line();
|
||||
|
||||
/*
|
||||
**Interface for serial port input.**
|
||||
*/
|
||||
void set_dcd(int port, bool level);
|
||||
|
||||
private:
|
||||
class Channel {
|
||||
public:
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
void set_dcd(bool level);
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
|
||||
enum class Parity {
|
||||
Even, Odd, Off
|
||||
} parity_ = Parity::Off;
|
||||
|
||||
enum class StopBits {
|
||||
Synchronous, OneBit, OneAndAHalfBits, TwoBits
|
||||
} stop_bits_ = StopBits::Synchronous;
|
||||
|
||||
enum class Sync {
|
||||
Monosync, Bisync, SDLC, External
|
||||
} sync_mode_ = Sync::Monosync;
|
||||
|
||||
int clock_rate_multiplier_ = 1;
|
||||
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
|
||||
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
bool external_status_interrupt_ = false;
|
||||
uint8_t external_interrupt_status_ = 0;
|
||||
|
||||
bool dcd_ = false;
|
||||
} channels_[2];
|
||||
|
||||
uint8_t pointer_ = 0;
|
||||
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
|
||||
uint8_t master_interrupt_control_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* z8530_hpp */
|
@ -22,7 +22,7 @@ namespace {
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
|
||||
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
|
||||
{
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
@ -211,7 +211,7 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
|
||||
drives_[drive].set_disk(disk);
|
||||
}
|
||||
|
||||
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
|
||||
void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
|
||||
inputs_ &= ~input_flux;
|
||||
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
|
||||
|
@ -98,7 +98,7 @@ class DiskII:
|
||||
void select_drive(int drive);
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Track::Event &event) override;
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
|
||||
|
||||
const int clock_rate_ = 0;
|
||||
|
375
Components/DiskII/IWM.cpp
Normal file
375
Components/DiskII/IWM.cpp
Normal file
@ -0,0 +1,375 @@
|
||||
//
|
||||
// IWM.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Apple;
|
||||
|
||||
namespace {
|
||||
const int CA0 = 1 << 0;
|
||||
const int CA1 = 1 << 1;
|
||||
const int CA2 = 1 << 2;
|
||||
const int LSTRB = 1 << 3;
|
||||
const int ENABLE = 1 << 4;
|
||||
const int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
|
||||
const int Q6 = 1 << 6;
|
||||
const int Q7 = 1 << 7;
|
||||
const int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
|
||||
}
|
||||
|
||||
IWM::IWM(int clock_rate) :
|
||||
clock_rate_(clock_rate) {}
|
||||
|
||||
// MARK: - Bus accessors
|
||||
|
||||
uint8_t IWM::read(int address) {
|
||||
access(address);
|
||||
|
||||
// Per Inside Macintosh:
|
||||
//
|
||||
// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
|
||||
// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
|
||||
// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
|
||||
// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
|
||||
//
|
||||
// My understanding:
|
||||
//
|
||||
// Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
|
||||
// the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
|
||||
|
||||
if(address&1) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default:
|
||||
LOG("[IWM] Invalid read\n");
|
||||
return 0xff;
|
||||
|
||||
// "Read all 1s".
|
||||
// printf("Reading all 1s\n");
|
||||
// return 0xff;
|
||||
|
||||
case 0:
|
||||
case ENABLE: { /* Read data register. Zeroing afterwards is a guess. */
|
||||
const auto result = data_register_;
|
||||
|
||||
if(data_register_ & 0x80) {
|
||||
// printf("\n\nIWM:%02x\n\n", data_register_);
|
||||
data_register_ = 0;
|
||||
}
|
||||
// LOG("Reading data register: " << PADHEX(2) << int(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case Q6: case Q6|ENABLE: {
|
||||
/*
|
||||
[If A = 0], Read status register:
|
||||
|
||||
bits 0-4: same as mode register.
|
||||
bit 5: 1 = either /ENBL1 or /ENBL2 is currently low.
|
||||
bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0).
|
||||
bit 7: 1 = SENSE input high; 0 = SENSE input low.
|
||||
|
||||
(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
|
||||
If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
|
||||
*/
|
||||
|
||||
return uint8_t(
|
||||
(mode_&0x1f) |
|
||||
((state_ & ENABLE) ? 0x20 : 0x00) |
|
||||
(sense() & 0x80)
|
||||
);
|
||||
} break;
|
||||
|
||||
case Q7: case Q7|ENABLE:
|
||||
/*
|
||||
Read write-handshake register:
|
||||
|
||||
bits 0-5: reserved for future use (currently read as 1).
|
||||
bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
|
||||
bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
|
||||
*/
|
||||
// LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_));
|
||||
return 0x3f | write_handshake_;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void IWM::write(int address, uint8_t input) {
|
||||
access(address);
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default: break;
|
||||
|
||||
case Q7|Q6:
|
||||
/*
|
||||
Write mode register:
|
||||
|
||||
bit 0: 1 = latch mode (should be set in asynchronous mode).
|
||||
bit 1: 0 = synchronous handshake protocol; 1 = asynchronous.
|
||||
bit 2: 0 = 1-second on-board timer enable; 1 = timer disable.
|
||||
bit 3: 0 = slow mode; 1 = fast mode.
|
||||
bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor).
|
||||
bit 5: 1 = test mode; 0 = normal operation.
|
||||
bit 6: 1 = MZ-reset.
|
||||
bit 7: reserved for future expansion.
|
||||
*/
|
||||
|
||||
mode_ = input;
|
||||
|
||||
switch(mode_ & 0x18) {
|
||||
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
|
||||
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
|
||||
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
|
||||
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
|
||||
}
|
||||
LOG("IWM mode is now " << PADHEX(2) << int(mode_));
|
||||
break;
|
||||
|
||||
case Q7|Q6|ENABLE: // Write data register.
|
||||
next_output_ = input;
|
||||
write_handshake_ &= ~0x80;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Switch access
|
||||
|
||||
void IWM::access(int address) {
|
||||
// Keep a record of switch state; bits in state_
|
||||
// should correlate with the anonymous namespace constants
|
||||
// defined at the top of this file — CA0, CA1, etc.
|
||||
address &= 0xf;
|
||||
const auto mask = 1 << (address >> 1);
|
||||
const auto old_state = state_;
|
||||
|
||||
if(address & 1) {
|
||||
state_ |= mask;
|
||||
} else {
|
||||
state_ &= ~mask;
|
||||
}
|
||||
|
||||
// React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode.
|
||||
if(old_state != state_) {
|
||||
push_drive_state();
|
||||
|
||||
switch(mask) {
|
||||
default: break;
|
||||
|
||||
case ENABLE:
|
||||
if(address & 1) {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true);
|
||||
} else {
|
||||
// If the 1-second delay is enabled, set up a timer for that.
|
||||
if(!(mode_ & 4)) {
|
||||
cycles_until_disable_ = Cycles(clock_rate_);
|
||||
} else {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DRIVESEL: {
|
||||
const int new_drive = address & 1;
|
||||
if(new_drive != active_drive_) {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
|
||||
active_drive_ = new_drive;
|
||||
if(drives_[active_drive_]) {
|
||||
drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0)));
|
||||
push_drive_state();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case Q6:
|
||||
case Q7:
|
||||
select_shift_mode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::set_select(bool enabled) {
|
||||
// Store SEL as an extra state bit.
|
||||
if(enabled) state_ |= SEL;
|
||||
else state_ &= ~SEL;
|
||||
push_drive_state();
|
||||
}
|
||||
|
||||
void IWM::push_drive_state() {
|
||||
if(drives_[active_drive_]) {
|
||||
const uint8_t drive_control_lines =
|
||||
((state_ & CA0) ? IWMDrive::CA0 : 0) |
|
||||
((state_ & CA1) ? IWMDrive::CA1 : 0) |
|
||||
((state_ & CA2) ? IWMDrive::CA2 : 0) |
|
||||
((state_ & SEL) ? IWMDrive::SEL : 0) |
|
||||
((state_ & LSTRB) ? IWMDrive::LSTRB : 0);
|
||||
drives_[active_drive_]->set_control_lines(drive_control_lines);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Active logic
|
||||
|
||||
void IWM::run_for(const Cycles cycles) {
|
||||
// Check for a timeout of the motor-off timer.
|
||||
if(cycles_until_disable_ > Cycles(0)) {
|
||||
cycles_until_disable_ -= cycles;
|
||||
if(cycles_until_disable_ <= Cycles(0)) {
|
||||
cycles_until_disable_ = Cycles(0);
|
||||
if(drives_[active_drive_])
|
||||
drives_[active_drive_]->set_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Activity otherwise depends on mode and motor state.
|
||||
int integer_cycles = cycles.as_int();
|
||||
switch(shift_mode_) {
|
||||
case ShiftMode::Reading:
|
||||
if(drive_is_rotating_[active_drive_]) {
|
||||
while(integer_cycles--) {
|
||||
drives_[active_drive_]->run_for(Cycles(1));
|
||||
++cycles_since_shift_;
|
||||
if(cycles_since_shift_ == bit_length_ + Cycles(2)) {
|
||||
propose_shift(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) {
|
||||
propose_shift(0);
|
||||
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
|
||||
}
|
||||
cycles_since_shift_ += Cycles(integer_cycles);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShiftMode::Writing:
|
||||
if(drives_[active_drive_]->is_writing()) {
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
|
||||
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
|
||||
drives_[active_drive_]->run_for(cycles_until_write);
|
||||
|
||||
// Output a flux transition if the top bit is set.
|
||||
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
|
||||
shift_register_ <<= 1;
|
||||
|
||||
integer_cycles -= cycles_until_write.as_int();
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
|
||||
--output_bits_remaining_;
|
||||
if(!output_bits_remaining_) {
|
||||
if(!(write_handshake_ & 0x80)) {
|
||||
write_handshake_ |= 0x80;
|
||||
shift_register_ = next_output_;
|
||||
output_bits_remaining_ = 8;
|
||||
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
|
||||
} else {
|
||||
write_handshake_ &= ~0x40;
|
||||
drives_[active_drive_]->end_writing();
|
||||
printf("\n");
|
||||
LOG("Overrun; done.");
|
||||
select_shift_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cycles_since_shift_ = integer_cycles;
|
||||
if(integer_cycles) {
|
||||
drives_[active_drive_]->run_for(cycles_since_shift_);
|
||||
}
|
||||
} else {
|
||||
drives_[active_drive_]->run_for(cycles);
|
||||
}
|
||||
break;
|
||||
|
||||
case ShiftMode::CheckingWriteProtect:
|
||||
if(integer_cycles < 8) {
|
||||
shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles)));
|
||||
} else {
|
||||
shift_register_ = sense();
|
||||
}
|
||||
|
||||
/* Deliberate fallthrough. */
|
||||
default:
|
||||
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::select_shift_mode() {
|
||||
// Don't allow an ongoing write to be interrupted.
|
||||
if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return;
|
||||
|
||||
const auto old_shift_mode = shift_mode_;
|
||||
|
||||
switch(state_ & (Q6|Q7)) {
|
||||
default: shift_mode_ = ShiftMode::CheckingWriteProtect; break;
|
||||
case 0: shift_mode_ = ShiftMode::Reading; break;
|
||||
case Q7:
|
||||
// "The IWM is put into the write state by a transition from the write protect sense state to the
|
||||
// write load state".
|
||||
if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing;
|
||||
break;
|
||||
}
|
||||
|
||||
// If writing mode just began, set the drive into write mode and cue up the first output byte.
|
||||
if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
|
||||
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false);
|
||||
shift_register_ = next_output_;
|
||||
write_handshake_ |= 0x80 | 0x40;
|
||||
output_bits_remaining_ = 8;
|
||||
LOG("Seeding output with " << PADHEX(2) << shift_register_);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IWM::sense() {
|
||||
return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff;
|
||||
}
|
||||
|
||||
void IWM::process_event(const Storage::Disk::Drive::Event &event) {
|
||||
if(shift_mode_ != ShiftMode::Reading) return;
|
||||
|
||||
switch(event.type) {
|
||||
case Storage::Disk::Track::Event::IndexHole: return;
|
||||
case Storage::Disk::Track::Event::FluxTransition:
|
||||
propose_shift(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::propose_shift(uint8_t bit) {
|
||||
// TODO: synchronous mode.
|
||||
|
||||
// LOG("Shifting input");
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
if(shift_register_ & 0x80) {
|
||||
data_register_ = shift_register_;
|
||||
shift_register_ = 0;
|
||||
}
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
}
|
||||
|
||||
void IWM::set_drive(int slot, IWMDrive *drive) {
|
||||
drives_[slot] = drive;
|
||||
drive->set_event_delegate(this);
|
||||
drive->set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
|
||||
const bool is_rotating = clocking != ClockingHint::Preference::None;
|
||||
|
||||
if(component == static_cast<ClockingHint::Source *>(drives_[0])) {
|
||||
drive_is_rotating_[0] = is_rotating;
|
||||
} else {
|
||||
drive_is_rotating_[1] = is_rotating;
|
||||
}
|
||||
}
|
117
Components/DiskII/IWM.hpp
Normal file
117
Components/DiskII/IWM.hpp
Normal file
@ -0,0 +1,117 @@
|
||||
//
|
||||
// IWM.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef IWM_hpp
|
||||
#define IWM_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
/*!
|
||||
Defines the drive interface used by the IWM, derived from the external pinout as
|
||||
per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
|
||||
|
||||
These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
|
||||
and provide the usual read/write interface for on-disk data.
|
||||
*/
|
||||
struct IWMDrive: public Storage::Disk::Drive {
|
||||
IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {}
|
||||
|
||||
enum Line: int {
|
||||
CA0 = 1 << 0,
|
||||
CA1 = 1 << 1,
|
||||
CA2 = 1 << 2,
|
||||
LSTRB = 1 << 3,
|
||||
SEL = 1 << 4,
|
||||
};
|
||||
|
||||
virtual void set_enabled(bool) = 0;
|
||||
virtual void set_control_lines(int) = 0;
|
||||
virtual bool read() = 0;
|
||||
};
|
||||
|
||||
class IWM:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
IWM(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/*!
|
||||
Submits an access to address @c address.
|
||||
|
||||
@returns The 8-bit value loaded to the data bus by the IWM.
|
||||
*/
|
||||
uint8_t read(int address);
|
||||
|
||||
/*!
|
||||
Sets the current input of the IWM's SEL line.
|
||||
*/
|
||||
void set_select(bool enabled);
|
||||
|
||||
/// Advances the controller by @c cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
/// Connects a drive to the IWM.
|
||||
void set_drive(int slot, IWMDrive *drive);
|
||||
|
||||
private:
|
||||
// Storage::Disk::Drive::EventDelegate.
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
|
||||
const int clock_rate_;
|
||||
|
||||
uint8_t data_register_ = 0;
|
||||
uint8_t mode_ = 0;
|
||||
bool read_write_ready_ = true;
|
||||
bool write_overran_ = false;
|
||||
|
||||
int state_ = 0;
|
||||
|
||||
int active_drive_ = 0;
|
||||
IWMDrive *drives_[2] = {nullptr, nullptr};
|
||||
bool drive_is_rotating_[2] = {false, false};
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
|
||||
Cycles cycles_until_disable_;
|
||||
uint8_t write_handshake_ = 0x80;
|
||||
|
||||
void access(int address);
|
||||
|
||||
uint8_t shift_register_ = 0;
|
||||
uint8_t next_output_ = 0;
|
||||
int output_bits_remaining_ = 0;
|
||||
|
||||
void propose_shift(uint8_t bit);
|
||||
Cycles cycles_since_shift_;
|
||||
Cycles bit_length_;
|
||||
|
||||
void push_drive_state();
|
||||
|
||||
enum class ShiftMode {
|
||||
Reading,
|
||||
Writing,
|
||||
CheckingWriteProtect
|
||||
} shift_mode_;
|
||||
|
||||
uint8_t sense();
|
||||
void select_shift_mode();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* IWM_hpp */
|
170
Components/DiskII/MacintoshDoubleDensityDrive.cpp
Normal file
170
Components/DiskII/MacintoshDoubleDensityDrive.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
//
|
||||
// MacintoshDoubleDensityDrive.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MacintoshDoubleDensityDrive.hpp"
|
||||
|
||||
/*
|
||||
Sources used pervasively:
|
||||
|
||||
http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html
|
||||
Apple Guide to the Macintosh Family Hardware
|
||||
Inside Macintosh III
|
||||
*/
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
|
||||
IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided.
|
||||
is_800k_(is_800k) {
|
||||
// Start with a valid rotation speed.
|
||||
if(is_800k) {
|
||||
set_rotation_speed(393.3807f);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Speed Selection
|
||||
|
||||
void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
|
||||
// The 800kb drive automatically selects rotation speed as a function of
|
||||
// head position; the 400kb drive doesn't do so.
|
||||
if(is_800k_) {
|
||||
/*
|
||||
Numbers below cribbed from the Kryoflux forums; specifically:
|
||||
https://forum.kryoflux.com/viewtopic.php?t=1090
|
||||
|
||||
They can almost be worked out algorithmically, since the point is to
|
||||
produce an almost-constant value for speed*(number of sectors), and:
|
||||
|
||||
393.3807 * 12 = 4720.5684
|
||||
429.1723 * 11 = 4720.895421
|
||||
472.1435 * 10 = 4721.435
|
||||
524.5672 * 9 = 4721.1048
|
||||
590.1098 * 8 = 4720.8784
|
||||
|
||||
So 4721 / (number of sectors per track in zone) would give essentially
|
||||
the same results.
|
||||
*/
|
||||
const int zone = to_position.as_int() >> 4;
|
||||
switch(zone) {
|
||||
case 0: set_rotation_speed(393.3807f); break;
|
||||
case 1: set_rotation_speed(429.1723f); break;
|
||||
case 2: set_rotation_speed(472.1435f); break;
|
||||
case 3: set_rotation_speed(524.5672f); break;
|
||||
default: set_rotation_speed(590.1098f); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Control input/output.
|
||||
|
||||
void DoubleDensityDrive::set_enabled(bool) {
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::set_control_lines(int lines) {
|
||||
const auto old_state = control_state_;
|
||||
control_state_ = lines;
|
||||
|
||||
// Catch low-to-high LSTRB transitions.
|
||||
if((old_state ^ control_state_) & control_state_ & Line::LSTRB) {
|
||||
switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case 0: // Set step direction — CA2 set => step outward.
|
||||
case Line::CA2:
|
||||
step_direction_ = (control_state_ & Line::CA2) ? -1 : 1;
|
||||
break;
|
||||
|
||||
case Line::CA1: // Set drive motor — CA2 set => motor off.
|
||||
case Line::CA1|Line::CA2:
|
||||
set_motor_on(!(control_state_ & Line::CA2));
|
||||
break;
|
||||
|
||||
case Line::CA0: // Initiate a step.
|
||||
step(Storage::Disk::HeadPosition(step_direction_));
|
||||
break;
|
||||
|
||||
case Line::SEL|Line::CA2: // Reset new disk flag.
|
||||
has_new_disk_ = false;
|
||||
break;
|
||||
|
||||
case Line::CA2 | Line::CA1 | Line::CA0: // Eject the disk.
|
||||
set_disk(nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DoubleDensityDrive::read() {
|
||||
switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) {
|
||||
default:
|
||||
return false;
|
||||
|
||||
case 0: // Head step direction.
|
||||
// (0 = inward)
|
||||
return step_direction_ <= 0;
|
||||
|
||||
case SEL: // Disk in place.
|
||||
// (0 = disk present)
|
||||
return !has_disk();
|
||||
|
||||
case CA0: // Disk head step completed.
|
||||
// (0 = still stepping)
|
||||
return true; // TODO: stepping delay. But at the main Drive level.
|
||||
|
||||
case CA0|SEL: // Disk locked.
|
||||
// (0 = write protected)
|
||||
return !get_is_read_only();
|
||||
|
||||
case CA1: // Disk motor running.
|
||||
// (0 = motor on)
|
||||
return !get_motor_on();
|
||||
|
||||
case CA1|SEL: // Head at track 0.
|
||||
// (0 = at track 0)
|
||||
// "This bit becomes valid beginning 12 msec after the step that places the head at track 0."
|
||||
return !get_is_track_zero();
|
||||
|
||||
case CA1|CA0: // Disk has been ejected.
|
||||
// (0 = user has ejected disk)
|
||||
return !has_new_disk_;
|
||||
|
||||
case CA1|CA0|SEL: // Tachometer.
|
||||
// (arbitrary)
|
||||
return get_tachometer();
|
||||
|
||||
case CA2: // Read data, lower head.
|
||||
set_head(0);
|
||||
return false;
|
||||
|
||||
case CA2|SEL: // Read data, upper head.
|
||||
set_head(1);
|
||||
return false;
|
||||
|
||||
case CA2|CA1: // Single- or double-sided drive.
|
||||
// (0 = single sided)
|
||||
return get_head_count() != 1;
|
||||
|
||||
case CA2|CA1|CA0: // "Present/HD" (per the Mac Plus ROM)
|
||||
// (0 = ??HD??)
|
||||
//
|
||||
// Alternative explanation: "Disk ready for reading?"
|
||||
// (0 = ready)
|
||||
return false;
|
||||
|
||||
case CA2|CA1|CA0|SEL: // Drive installed.
|
||||
// (0 = present, 1 = missing)
|
||||
//
|
||||
// TODO: why do I need to return this the wrong way around for the Mac Plus?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::did_set_disk() {
|
||||
has_new_disk_ = true;
|
||||
}
|
39
Components/DiskII/MacintoshDoubleDensityDrive.hpp
Normal file
39
Components/DiskII/MacintoshDoubleDensityDrive.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// MacintoshDoubleDensityDrive.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MacintoshDoubleDensityDrive_hpp
|
||||
#define MacintoshDoubleDensityDrive_hpp
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class DoubleDensityDrive: public IWMDrive {
|
||||
public:
|
||||
DoubleDensityDrive(int input_clock_rate, bool is_800k);
|
||||
|
||||
void set_enabled(bool) override;
|
||||
void set_control_lines(int) override;
|
||||
bool read() override;
|
||||
|
||||
private:
|
||||
// To receive the proper notifications from Storage::Disk::Drive.
|
||||
void did_step(Storage::Disk::HeadPosition to_position) override;
|
||||
void did_set_disk() override;
|
||||
|
||||
const bool is_800k_;
|
||||
bool has_new_disk_ = false;
|
||||
int control_state_ = 0;
|
||||
int step_direction_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MacintoshDoubleDensityDrive_hpp */
|
@ -6,8 +6,8 @@
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Keyboard_hpp
|
||||
#define Keyboard_hpp
|
||||
#ifndef Inputs_Keyboard_hpp
|
||||
#define Inputs_Keyboard_hpp
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
@ -75,4 +75,4 @@ class Keyboard {
|
||||
|
||||
}
|
||||
|
||||
#endif /* Keyboard_hpp */
|
||||
#endif /* Inputs_Keyboard_hpp */
|
||||
|
47
Inputs/Mouse.hpp
Normal file
47
Inputs/Mouse.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// Mouse.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Mouse_h
|
||||
#define Mouse_h
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
/*!
|
||||
Models a classic-era mouse: something that provides 2d relative motion plus
|
||||
some quantity of buttons.
|
||||
*/
|
||||
class Mouse {
|
||||
public:
|
||||
/*!
|
||||
Indicates a movement of the mouse.
|
||||
*/
|
||||
virtual void move(int x, int y) {}
|
||||
|
||||
/*!
|
||||
@returns the number of buttons on this mouse.
|
||||
*/
|
||||
virtual int get_number_of_buttons() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Indicates that button @c index is now either pressed or unpressed.
|
||||
The intention is that @c index be semantic, not positional:
|
||||
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
|
||||
*/
|
||||
virtual void set_button_pressed(int index, bool is_pressed) {}
|
||||
|
||||
/*!
|
||||
Releases all depressed buttons.
|
||||
*/
|
||||
virtual void reset_all_buttons() {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Mouse_h */
|
123
Inputs/QuadratureMouse/QuadratureMouse.hpp
Normal file
123
Inputs/QuadratureMouse/QuadratureMouse.hpp
Normal file
@ -0,0 +1,123 @@
|
||||
//
|
||||
// QuadratureMouse.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef QuadratureMouse_hpp
|
||||
#define QuadratureMouse_hpp
|
||||
|
||||
#include "../Mouse.hpp"
|
||||
#include <atomic>
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
/*!
|
||||
Provides a simple implementation of a Mouse, designed for simple
|
||||
thread-safe feeding to a machine that accepts quadrature-encoded input.
|
||||
|
||||
TEMPORARY SIMPLIFICATION: it is assumed that the caller will be interested
|
||||
in observing a signal that dictates velocity, sampling the other to
|
||||
obtain direction only on transitions in the velocity signal.
|
||||
|
||||
Or, more concretely, of the two channels per axis, one is accurate only when
|
||||
the other transitions. Hence the discussion of 'primary' and 'secondary'
|
||||
channels below. This is intended to be fixed.
|
||||
*/
|
||||
class QuadratureMouse: public Mouse {
|
||||
public:
|
||||
QuadratureMouse(int number_of_buttons) :
|
||||
number_of_buttons_(number_of_buttons) {}
|
||||
|
||||
/*
|
||||
Inputs, to satisfy the Mouse interface.
|
||||
*/
|
||||
void move(int x, int y) override {
|
||||
// Accumulate all provided motion.
|
||||
axes_[0] += x;
|
||||
axes_[1] += y;
|
||||
}
|
||||
|
||||
int get_number_of_buttons() override {
|
||||
return number_of_buttons_;
|
||||
}
|
||||
|
||||
void set_button_pressed(int index, bool is_pressed) override {
|
||||
if(is_pressed)
|
||||
button_flags_ |= (1 << index);
|
||||
else
|
||||
button_flags_ &= ~(1 << index);
|
||||
}
|
||||
|
||||
void reset_all_buttons() override {
|
||||
button_flags_ = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Outputs.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Applies a single step from the current accumulated mouse movement, which
|
||||
might involve the mouse moving right, or left, or not at all.
|
||||
*/
|
||||
void prepare_step() {
|
||||
for(int axis = 0; axis < 2; ++axis) {
|
||||
// Do nothing if there's no motion to communicate.
|
||||
const int axis_value = axes_[axis];
|
||||
if(!axis_value) continue;
|
||||
|
||||
// Toggle the primary channel and set the secondary for
|
||||
// negative motion. At present the y axis signals the
|
||||
// secondary channel the opposite way around from the
|
||||
// primary.
|
||||
primaries_[axis] ^= 1;
|
||||
secondaries_[axis] = primaries_[axis] ^ axis;
|
||||
if(axis_value > 0) {
|
||||
-- axes_[axis];
|
||||
secondaries_[axis] ^= 1; // Switch to positive motion.
|
||||
} else {
|
||||
++ axes_[axis];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the two quadrature channels — bit 0 is the 'primary' channel
|
||||
(i.e. the one that can be monitored to observe velocity) and
|
||||
bit 1 is the 'secondary' (i.e. that which can be queried to
|
||||
observe direction).
|
||||
*/
|
||||
int get_channel(int axis) {
|
||||
return primaries_[axis] | (secondaries_[axis] << 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns a bit mask of the currently pressed buttons.
|
||||
*/
|
||||
int get_button_mask() {
|
||||
return button_flags_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns @c true if any mouse motion is waiting to be communicated;
|
||||
@c false otherwise.
|
||||
*/
|
||||
bool has_steps() {
|
||||
return axes_[0] || axes_[1];
|
||||
}
|
||||
|
||||
private:
|
||||
int number_of_buttons_ = 0;
|
||||
std::atomic<int> button_flags_;
|
||||
std::atomic<int> axes_[2];
|
||||
|
||||
int primaries_[2] = {0, 0};
|
||||
int secondaries_[2] = {0, 0};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* QuadratureMouse_hpp */
|
@ -8,33 +8,34 @@
|
||||
|
||||
#include "AppleII.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../MediaTarget.hpp"
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../JoystickMachine.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
#include "../Utility/StringSerialiser.hpp"
|
||||
#include "../../../Activity/Source.hpp"
|
||||
#include "../../MediaTarget.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../JoystickMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
#include "../../Utility/StringSerialiser.hpp"
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Components/AudioToggle/AudioToggle.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../Components/AudioToggle/AudioToggle.hpp"
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Log.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "DiskIICard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../../Configurable/StandardOptions.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
return Configurable::standard_options(
|
||||
@ -51,12 +52,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public Configurable::Device,
|
||||
public AppleII::Machine,
|
||||
public Apple::II::Machine,
|
||||
public Activity::Source,
|
||||
public JoystickMachine::Machine,
|
||||
public AppleII::Card::Delegate {
|
||||
public Apple::II::Card::Delegate {
|
||||
private:
|
||||
struct VideoBusHandler : public AppleII::Video::BusHandler {
|
||||
struct VideoBusHandler : public Apple::II::Video::BusHandler {
|
||||
public:
|
||||
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
|
||||
|
||||
@ -71,7 +72,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
VideoBusHandler video_bus_handler_;
|
||||
AppleII::Video::Video<VideoBusHandler, is_iie()> video_;
|
||||
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
|
||||
int cycles_into_current_line_ = 0;
|
||||
Cycles cycles_since_video_update_;
|
||||
|
||||
@ -109,28 +110,28 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
Cycles cycles_since_audio_update_;
|
||||
|
||||
// MARK: - Cards
|
||||
std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
|
||||
std::array<std::unique_ptr<Apple::II::Card>, 7> cards_;
|
||||
Cycles cycles_since_card_update_;
|
||||
std::vector<AppleII::Card *> every_cycle_cards_;
|
||||
std::vector<AppleII::Card *> just_in_time_cards_;
|
||||
std::vector<Apple::II::Card *> every_cycle_cards_;
|
||||
std::vector<Apple::II::Card *> just_in_time_cards_;
|
||||
|
||||
int stretched_cycles_since_card_update_ = 0;
|
||||
|
||||
void install_card(std::size_t slot, AppleII::Card *card) {
|
||||
void install_card(std::size_t slot, Apple::II::Card *card) {
|
||||
assert(slot >= 1 && slot < 8);
|
||||
cards_[slot - 1].reset(card);
|
||||
card->set_delegate(this);
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
bool is_every_cycle_card(AppleII::Card *card) {
|
||||
bool is_every_cycle_card(Apple::II::Card *card) {
|
||||
return !card->get_select_constraints();
|
||||
}
|
||||
|
||||
void pick_card_messaging_group(AppleII::Card *card) {
|
||||
void pick_card_messaging_group(Apple::II::Card *card) {
|
||||
const bool is_every_cycle = is_every_cycle_card(card);
|
||||
std::vector<AppleII::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
|
||||
std::vector<AppleII::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
|
||||
std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
|
||||
std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
|
||||
|
||||
if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
|
||||
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
|
||||
@ -138,12 +139,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
intended.push_back(card);
|
||||
}
|
||||
|
||||
void card_did_change_select_constraints(AppleII::Card *card) override {
|
||||
void card_did_change_select_constraints(Apple::II::Card *card) override {
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
AppleII::DiskIICard *diskii_card() {
|
||||
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
|
||||
Apple::II::DiskIICard *diskii_card() {
|
||||
return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get());
|
||||
}
|
||||
|
||||
// MARK: - Memory Map.
|
||||
@ -383,7 +384,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
}
|
||||
|
||||
// Set up the default memory blocks. On a II or II+ these values will never change.
|
||||
@ -700,7 +701,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
// If this is a card access, figure out which card is at play before determining
|
||||
// the totality of who needs messaging.
|
||||
size_t card_number = 0;
|
||||
AppleII::Card::Select select = AppleII::Card::None;
|
||||
Apple::II::Card::Select select = Apple::II::Card::None;
|
||||
|
||||
if(address >= 0xc100) {
|
||||
/*
|
||||
@ -708,20 +709,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
0xCn00 to 0xCnff: card n.
|
||||
*/
|
||||
card_number = (address - 0xc100) >> 8;
|
||||
select = AppleII::Card::Device;
|
||||
select = Apple::II::Card::Device;
|
||||
} else {
|
||||
/*
|
||||
Decode the area conventionally used by cards for registers:
|
||||
C0n0 to C0nF: card n - 8.
|
||||
*/
|
||||
card_number = (address - 0xc090) >> 4;
|
||||
select = AppleII::Card::IO;
|
||||
select = Apple::II::Card::IO;
|
||||
}
|
||||
|
||||
// If the selected card is a just-in-time card, update the just-in-time cards,
|
||||
// and then message it specifically.
|
||||
const bool is_read = isReadOperation(operation);
|
||||
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
|
||||
Apple::II::Card *const target = cards_[static_cast<size_t>(card_number)].get();
|
||||
if(target && !is_every_cycle_card(target)) {
|
||||
update_just_in_time_cards();
|
||||
target->perform_bus_operation(select, is_read, address, value);
|
||||
@ -732,7 +733,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
for(const auto &card: every_cycle_cards_) {
|
||||
card->run_for(Cycles(1), is_stretched_cycle);
|
||||
card->perform_bus_operation(
|
||||
(card == target) ? select : AppleII::Card::None,
|
||||
(card == target) ? select : Apple::II::Card::None,
|
||||
is_read, address, value);
|
||||
}
|
||||
has_updated_cards = true;
|
||||
@ -744,7 +745,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
const bool is_read = isReadOperation(operation);
|
||||
for(const auto &card: every_cycle_cards_) {
|
||||
card->run_for(Cycles(1), is_stretched_cycle);
|
||||
card->perform_bus_operation(AppleII::Card::None, is_read, address, value);
|
||||
card->perform_bus_operation(Apple::II::Card::None, is_read, address, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -818,7 +819,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
// MARK:: Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
return AppleII::get_options();
|
||||
return Apple::II::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
@ -860,9 +861,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace AppleII;
|
||||
using namespace Apple::II;
|
||||
|
||||
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
@ -9,14 +9,15 @@
|
||||
#ifndef AppleII_hpp
|
||||
#define AppleII_hpp
|
||||
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
#include "../../../Configurable/Configurable.hpp"
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
/// @returns The options available for an Apple II.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
@ -29,6 +30,7 @@ class Machine {
|
||||
static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AppleII_hpp */
|
@ -9,11 +9,12 @@
|
||||
#ifndef Card_h
|
||||
#define Card_h
|
||||
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Activity/Observer.hpp"
|
||||
#include "../../../Processors/6502/6502.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Activity/Observer.hpp"
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
/*!
|
||||
This provides a small subset of the interface offered to cards installed in
|
||||
@ -109,6 +110,7 @@ class Card {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Card_h */
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "DiskIICard.hpp"
|
||||
|
||||
using namespace AppleII;
|
||||
using namespace Apple::II;
|
||||
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
|
||||
const auto roms = rom_fetcher(
|
@ -10,17 +10,18 @@
|
||||
#define DiskIICard_hpp
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
#include "../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
@ -41,6 +42,7 @@ class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DiskIICard_hpp */
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
using namespace AppleII::Video;
|
||||
using namespace Apple::II::Video;
|
||||
|
||||
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
|
@ -9,14 +9,15 @@
|
||||
#ifndef Video_hpp
|
||||
#define Video_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockDeferrer.hpp"
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../ClockReceiver/ClockDeferrer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace AppleII {
|
||||
namespace Apple {
|
||||
namespace II {
|
||||
namespace Video {
|
||||
|
||||
class BusHandler {
|
||||
@ -600,6 +601,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
BusHandler &bus_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
97
Machines/Apple/Macintosh/Audio.cpp
Normal file
97
Machines/Apple/Macintosh/Audio.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
//
|
||||
// Audio.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Audio.hpp"
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
|
||||
// The sample_length is coupled with the clock rate selected within the Macintosh proper;
|
||||
// as per the header-declaration a divide-by-two clock is expected to arrive here.
|
||||
const std::size_t sample_length = 352 / 2;
|
||||
|
||||
}
|
||||
|
||||
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
// MARK: - Inputs
|
||||
|
||||
void Audio::post_sample(uint8_t sample) {
|
||||
// Store sample directly indexed by current write pointer; this ensures that collected samples
|
||||
// directly map to volume and enabled/disabled states.
|
||||
sample_queue_.buffer[sample_queue_.write_pointer] = sample;
|
||||
sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size();
|
||||
}
|
||||
|
||||
void Audio::set_volume(int volume) {
|
||||
// Do nothing if the volume hasn't changed.
|
||||
if(posted_volume_ == volume) return;
|
||||
posted_volume_ = volume;
|
||||
|
||||
// Post the volume change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
volume_ = volume;
|
||||
set_volume_multiplier();
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::set_enabled(bool on) {
|
||||
// Do nothing if the mask hasn't changed.
|
||||
if(posted_enable_mask_ == int(on)) return;
|
||||
posted_enable_mask_ = int(on);
|
||||
|
||||
// Post the enabled mask change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
enabled_mask_ = int(on);
|
||||
set_volume_multiplier();
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Output generation
|
||||
|
||||
bool Audio::is_zero_level() {
|
||||
return !volume_ || !enabled_mask_;
|
||||
}
|
||||
|
||||
void Audio::set_sample_volume_range(std::int16_t range) {
|
||||
// Some underflow here doesn't really matter.
|
||||
output_volume_ = range / (7 * 255);
|
||||
set_volume_multiplier();
|
||||
}
|
||||
|
||||
void Audio::set_volume_multiplier() {
|
||||
volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_);
|
||||
}
|
||||
|
||||
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation;
|
||||
// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
|
||||
// that's something to return to.
|
||||
|
||||
while(number_of_samples) {
|
||||
// Determine how many output samples will be at the same level.
|
||||
const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_);
|
||||
|
||||
// Determine the output level, and output that many samples.
|
||||
// (Hoping that the copiler substitutes an effective memset16-type operation here).
|
||||
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128);
|
||||
for(size_t c = 0; c < cycles_left_in_sample; ++c) {
|
||||
target[c] = output_level;
|
||||
}
|
||||
target += cycles_left_in_sample;
|
||||
|
||||
// Advance the sample pointer.
|
||||
subcycle_offset_ += cycles_left_in_sample;
|
||||
sample_queue_.read_pointer = (sample_queue_.read_pointer + (subcycle_offset_ / sample_length)) % sample_queue_.buffer.size();
|
||||
subcycle_offset_ %= sample_length;
|
||||
|
||||
// Decreate the number of samples left to write.
|
||||
number_of_samples -= cycles_left_in_sample;
|
||||
}
|
||||
}
|
88
Machines/Apple/Macintosh/Audio.hpp
Normal file
88
Machines/Apple/Macintosh/Audio.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// Audio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Audio_hpp
|
||||
#define Audio_hpp
|
||||
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Implements the Macintosh's audio output hardware.
|
||||
|
||||
Designed to be clocked at half the rate of the real hardware — i.e.
|
||||
a shade less than 4Mhz.
|
||||
*/
|
||||
class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Audio(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
|
||||
/*!
|
||||
Macintosh audio is (partly) sourced by the same scanning
|
||||
hardware as the video; each line it collects an additional
|
||||
word of memory, half of which is used for audio output.
|
||||
|
||||
Use this method to add a newly-collected sample to the queue.
|
||||
*/
|
||||
void post_sample(uint8_t sample);
|
||||
|
||||
/*!
|
||||
Macintosh audio also separately receives an output volume
|
||||
level, in the range 0 to 7.
|
||||
|
||||
Use this method to set the current output volume.
|
||||
*/
|
||||
void set_volume(int volume);
|
||||
|
||||
/*!
|
||||
A further factor in audio output is the on-off toggle.
|
||||
*/
|
||||
void set_enabled(bool on);
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
bool is_zero_level();
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
// A queue of fetched samples; read from by one thread,
|
||||
// written to by another.
|
||||
struct {
|
||||
std::array<uint8_t, 740> buffer;
|
||||
size_t read_pointer = 0, write_pointer = 0;
|
||||
} sample_queue_;
|
||||
|
||||
// Emulator-thread stateful variables, to avoid work posting
|
||||
// deferral updates if possible.
|
||||
int posted_volume_ = 0;
|
||||
int posted_enable_mask_ = 0;
|
||||
|
||||
// Stateful variables, modified from the audio generation
|
||||
// thread only.
|
||||
int volume_ = 0;
|
||||
int enabled_mask_ = 0;
|
||||
std::int16_t output_volume_ = 0;
|
||||
|
||||
std::int16_t volume_multiplier_ = 0;
|
||||
std::size_t subcycle_offset_ = 0;
|
||||
void set_volume_multiplier();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Audio_hpp */
|
34
Machines/Apple/Macintosh/DeferredAudio.hpp
Normal file
34
Machines/Apple/Macintosh/DeferredAudio.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// DeferredAudio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DeferredAudio_h
|
||||
#define DeferredAudio_h
|
||||
|
||||
#include "Audio.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
struct DeferredAudio {
|
||||
Concurrency::DeferringAsyncTaskQueue queue;
|
||||
Audio audio;
|
||||
Outputs::Speaker::LowpassSpeaker<Audio> speaker;
|
||||
HalfCycles time_since_update;
|
||||
|
||||
DeferredAudio() : audio(queue), speaker(audio) {}
|
||||
|
||||
void flush() {
|
||||
speaker.run_for(queue, time_since_update.flush_cycles());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DeferredAudio_h */
|
28
Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
Normal file
28
Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// DriveSpeedAccumulator.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "DriveSpeedAccumulator.hpp"
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
void DriveSpeedAccumulator::post_sample(uint8_t sample) {
|
||||
// An Euler-esque approximation is used here: just collect all
|
||||
// the samples until there is a certain small quantity of them,
|
||||
// then produce a new estimate of rotation speed and start the
|
||||
// buffer afresh.
|
||||
samples_[sample_pointer_] = sample;
|
||||
++sample_pointer_;
|
||||
|
||||
if(sample_pointer_ == samples_.size()) {
|
||||
sample_pointer_ = 0;
|
||||
// for(int c = 0; c < 512; c += 32) {
|
||||
// printf("%u ", samples_[c]);
|
||||
// }
|
||||
// printf("\n");
|
||||
}
|
||||
}
|
34
Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
Normal file
34
Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// DriveSpeedAccumulator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DriveSpeedAccumulator_hpp
|
||||
#define DriveSpeedAccumulator_hpp
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class DriveSpeedAccumulator {
|
||||
public:
|
||||
/*!
|
||||
Accepts fetched motor control values.
|
||||
*/
|
||||
void post_sample(uint8_t sample);
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 512> samples_;
|
||||
std::size_t sample_pointer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DriveSpeedAccumulator_hpp */
|
294
Machines/Apple/Macintosh/Keyboard.hpp
Normal file
294
Machines/Apple/Macintosh/Keyboard.hpp
Normal file
@ -0,0 +1,294 @@
|
||||
//
|
||||
// Keyboard.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Apple_Macintosh_Keyboard_hpp
|
||||
#define Apple_Macintosh_Keyboard_hpp
|
||||
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class Keyboard {
|
||||
public:
|
||||
void set_input(bool data) {
|
||||
switch(mode_) {
|
||||
case Mode::Waiting:
|
||||
/*
|
||||
"Only the computer can initiate communication over the keyboard lines. When the computer and keyboard
|
||||
are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The
|
||||
computer signals that it is ready to begin communication by pulling the Keyboard Data line low."
|
||||
*/
|
||||
if(!data) {
|
||||
mode_ = Mode::AcceptingCommand;
|
||||
phase_ = 0;
|
||||
command_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Mode::AcceptingCommand:
|
||||
/* Note value, so that it can be latched upon a clock transition. */
|
||||
data_input_ = data;
|
||||
break;
|
||||
|
||||
case Mode::AwaitingEndOfCommand:
|
||||
/*
|
||||
The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready
|
||||
to receive the keyboard's response by setting the Keyboard Data line high.
|
||||
*/
|
||||
if(data) {
|
||||
mode_ = Mode::PerformingCommand;
|
||||
phase_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
case Mode::SendingResponse:
|
||||
/* This line isn't currently an input; do nothing. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool get_clock() {
|
||||
return clock_output_;
|
||||
}
|
||||
|
||||
bool get_data() {
|
||||
return !!(response_ & 0x80);
|
||||
}
|
||||
|
||||
/*!
|
||||
The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz.
|
||||
*/
|
||||
void run_for(HalfCycles cycle) {
|
||||
switch(mode_) {
|
||||
default:
|
||||
case Mode::Waiting: return;
|
||||
|
||||
case Mode::AcceptingCommand: {
|
||||
/*
|
||||
"When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low,
|
||||
220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places
|
||||
a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge
|
||||
of the Keyboard Clock signal."
|
||||
*/
|
||||
const auto offset = phase_ % 40;
|
||||
clock_output_ = offset >= 18;
|
||||
|
||||
if(offset == 26) {
|
||||
command_ = (command_ << 1) | (data_input_ ? 1 : 0);
|
||||
}
|
||||
|
||||
++phase_;
|
||||
if(phase_ == 8*40) {
|
||||
mode_ = Mode::AwaitingEndOfCommand;
|
||||
phase_ = 0;
|
||||
clock_output_ = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Mode::AwaitingEndOfCommand:
|
||||
// Time out if the end-of-command seems not to be forthcoming.
|
||||
// This is an elaboration on my part; a guess.
|
||||
++phase_;
|
||||
if(phase_ == 1000) {
|
||||
clock_output_ = false;
|
||||
mode_ = Mode::Waiting;
|
||||
phase_ = 0;
|
||||
}
|
||||
return;
|
||||
|
||||
case Mode::PerformingCommand: {
|
||||
response_ = perform_command(command_);
|
||||
|
||||
// Inquiry has a 0.25-second timeout; everything else is instant.
|
||||
++phase_;
|
||||
if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) {
|
||||
mode_ = Mode::SendingResponse;
|
||||
phase_ = 0;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Mode::SendingResponse: {
|
||||
/*
|
||||
"When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high)
|
||||
on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each
|
||||
clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the
|
||||
rising edge of the Keyboard Clock signal."
|
||||
*/
|
||||
const auto offset = phase_ % 33;
|
||||
clock_output_ = offset >= 16;
|
||||
|
||||
if(offset == 29) {
|
||||
response_ <<= 1;
|
||||
}
|
||||
|
||||
++phase_;
|
||||
if(phase_ == 8*33) {
|
||||
clock_output_ = false;
|
||||
mode_ = Mode::Waiting;
|
||||
phase_ = 0;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue_key_state(uint16_t key, bool is_pressed) {
|
||||
// Front insert; messages will be pop_back'd.
|
||||
std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
|
||||
|
||||
// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme
|
||||
// they are indicated by having bit 8 set. So add the $79 prefix if required.
|
||||
if(key & 0x100) {
|
||||
key_queue_.insert(key_queue_.begin(), 0x79);
|
||||
}
|
||||
key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int perform_command(int command) {
|
||||
switch(command) {
|
||||
case 0x10: // Inquiry.
|
||||
case 0x14: { // Instant.
|
||||
std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
|
||||
if(!key_queue_.empty()) {
|
||||
const auto new_message = key_queue_.back();
|
||||
key_queue_.pop_back();
|
||||
return new_message;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x16: // Model number.
|
||||
return
|
||||
0x01 | // b0: always 1
|
||||
(1 << 1) | // keyboard model number
|
||||
(1 << 4); // next device number
|
||||
// (b7 not set => no next device)
|
||||
|
||||
case 0x36: // Test
|
||||
return 0x7d; // 0x7d = ACK, 0x77 = not ACK.
|
||||
}
|
||||
return 0x7b; // No key transition.
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
Waiting,
|
||||
AcceptingCommand,
|
||||
AwaitingEndOfCommand,
|
||||
SendingResponse,
|
||||
PerformingCommand
|
||||
} mode_ = Mode::Waiting;
|
||||
int phase_ = 0;
|
||||
int command_ = 0;
|
||||
int response_ = 0;
|
||||
|
||||
bool data_input_ = false;
|
||||
bool clock_output_ = false;
|
||||
|
||||
// TODO: improve this very, very simple implementation.
|
||||
std::mutex key_queue_mutex_;
|
||||
std::vector<uint8_t> key_queue_;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a mapping from idiomatic PC keys to Macintosh keys.
|
||||
*/
|
||||
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override {
|
||||
using Key = Inputs::Keyboard::Key;
|
||||
switch(key) {
|
||||
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
|
||||
|
||||
/*
|
||||
See p284 of the Apple Guide to the Macintosh Family Hardware
|
||||
for documentation of the mapping below.
|
||||
*/
|
||||
|
||||
case Key::BackTick: return 0x65;
|
||||
case Key::k1: return 0x25;
|
||||
case Key::k2: return 0x27;
|
||||
case Key::k3: return 0x29;
|
||||
case Key::k4: return 0x2b;
|
||||
case Key::k5: return 0x2f;
|
||||
case Key::k6: return 0x2d;
|
||||
case Key::k7: return 0x35;
|
||||
case Key::k8: return 0x39;
|
||||
case Key::k9: return 0x33;
|
||||
case Key::k0: return 0x3b;
|
||||
case Key::Hyphen: return 0x37;
|
||||
case Key::Equals: return 0x31;
|
||||
case Key::BackSpace: return 0x67;
|
||||
|
||||
case Key::Tab: return 0x61;
|
||||
case Key::Q: return 0x19;
|
||||
case Key::W: return 0x1b;
|
||||
case Key::E: return 0x1d;
|
||||
case Key::R: return 0x1f;
|
||||
case Key::T: return 0x23;
|
||||
case Key::Y: return 0x21;
|
||||
case Key::U: return 0x41;
|
||||
case Key::I: return 0x45;
|
||||
case Key::O: return 0x3f;
|
||||
case Key::P: return 0x47;
|
||||
case Key::OpenSquareBracket: return 0x43;
|
||||
case Key::CloseSquareBracket: return 0x3d;
|
||||
|
||||
case Key::CapsLock: return 0x73;
|
||||
case Key::A: return 0x01;
|
||||
case Key::S: return 0x03;
|
||||
case Key::D: return 0x05;
|
||||
case Key::F: return 0x07;
|
||||
case Key::G: return 0x0b;
|
||||
case Key::H: return 0x09;
|
||||
case Key::J: return 0x4d;
|
||||
case Key::K: return 0x51;
|
||||
case Key::L: return 0x4b;
|
||||
case Key::Semicolon: return 0x53;
|
||||
case Key::Quote: return 0x4f;
|
||||
case Key::Enter: return 0x49;
|
||||
|
||||
case Key::LeftShift: return 0x71;
|
||||
case Key::Z: return 0x0d;
|
||||
case Key::X: return 0x0f;
|
||||
case Key::C: return 0x11;
|
||||
case Key::V: return 0x13;
|
||||
case Key::B: return 0x17;
|
||||
case Key::N: return 0x5b;
|
||||
case Key::M: return 0x5d;
|
||||
case Key::Comma: return 0x57;
|
||||
case Key::FullStop: return 0x5f;
|
||||
case Key::ForwardSlash: return 0x59;
|
||||
case Key::RightShift: return 0x71;
|
||||
|
||||
case Key::Left: return 0x100 | 0x0d;
|
||||
case Key::Right: return 0x100 | 0x05;
|
||||
case Key::Up: return 0x100 | 0x1b;
|
||||
case Key::Down: return 0x100 | 0x11;
|
||||
|
||||
case Key::LeftOption:
|
||||
case Key::RightOption: return 0x75;
|
||||
case Key::LeftMeta:
|
||||
case Key::RightMeta: return 0x6f;
|
||||
|
||||
case Key::Space: return 0x63;
|
||||
case Key::BackSlash: return 0x55;
|
||||
|
||||
/* TODO: the numeric keypad. */
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Apple_Macintosh_Keyboard_hpp */
|
621
Machines/Apple/Macintosh/Macintosh.cpp
Normal file
621
Machines/Apple/Macintosh/Macintosh.cpp
Normal file
@ -0,0 +1,621 @@
|
||||
//
|
||||
// Macintosh.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Macintosh.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "DeferredAudio.hpp"
|
||||
#include "DriveSpeedAccumulator.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "RealTimeClock.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../MediaTarget.hpp"
|
||||
#include "../../MouseMachine.hpp"
|
||||
|
||||
#include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp"
|
||||
|
||||
//#define LOG_TRACE
|
||||
|
||||
#include "../../../Components/6522/6522.hpp"
|
||||
#include "../../../Components/8530/z8530.hpp"
|
||||
#include "../../../Components/DiskII/IWM.hpp"
|
||||
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
|
||||
#include "../../../Analyser/Static/Macintosh/Target.hpp"
|
||||
|
||||
#include "../../Utility/MemoryPacker.hpp"
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
const int CLOCK_RATE = 7833600;
|
||||
|
||||
}
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
public MediaTarget::Machine,
|
||||
public MouseMachine::Machine,
|
||||
public CPU::MC68000::BusHandler,
|
||||
public KeyboardMachine::MappedMachine {
|
||||
public:
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
|
||||
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
iwm_(CLOCK_RATE),
|
||||
video_(ram_, audio_, drive_speed_accumulator_),
|
||||
via_(via_port_handler_),
|
||||
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
|
||||
drives_{
|
||||
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
|
||||
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
|
||||
},
|
||||
mouse_(1) {
|
||||
|
||||
// Select a ROM name and determine the proper ROM and RAM sizes
|
||||
// based on the machine model.
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
std::string rom_name;
|
||||
uint32_t ram_size, rom_size;
|
||||
switch(model) {
|
||||
default:
|
||||
case Model::Mac128k:
|
||||
ram_size = 128*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_name = "mac128k.rom";
|
||||
break;
|
||||
case Model::Mac512k:
|
||||
ram_size = 512*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_name = "mac512k.rom";
|
||||
break;
|
||||
case Model::Mac512ke:
|
||||
case Model::MacPlus:
|
||||
ram_size = 512*1024;
|
||||
rom_size = 128*1024;
|
||||
rom_name = "macplus.rom";
|
||||
break;
|
||||
}
|
||||
ram_mask_ = (ram_size >> 1) - 1;
|
||||
rom_mask_ = (rom_size >> 1) - 1;
|
||||
video_.set_ram_mask(ram_mask_);
|
||||
|
||||
// Grab a copy of the ROM and convert it into big-endian data.
|
||||
const auto roms = rom_fetcher("Macintosh", { rom_name });
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
roms[0]->resize(rom_size);
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
|
||||
// Randomise memory contents.
|
||||
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
|
||||
|
||||
// Attach the drives to the IWM.
|
||||
iwm_.iwm.set_drive(0, &drives_[0]);
|
||||
iwm_.iwm.set_drive(1, &drives_[1]);
|
||||
|
||||
// The Mac runs at 7.8336mHz.
|
||||
set_clock_rate(double(CLOCK_RATE));
|
||||
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
|
||||
|
||||
// Insert any supplied media.
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_.queue.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
video_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &audio_.speaker;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
|
||||
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
||||
// TODO: pick a delay if this is a video-clashing memory fetch.
|
||||
HalfCycles delay(0);
|
||||
|
||||
time_since_video_update_ += cycle.length;
|
||||
iwm_.time_since_update += cycle.length;
|
||||
|
||||
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
|
||||
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
|
||||
// may occur here in order to provide VSYNC at a proper moment.
|
||||
// Possibly route vsync.
|
||||
if(time_since_video_update_ < time_until_video_event_) {
|
||||
via_clock_ += cycle.length;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
} else {
|
||||
auto via_time_base = time_since_video_update_ - cycle.length;
|
||||
auto via_cycles_outstanding = cycle.length;
|
||||
while(time_until_video_event_ < time_since_video_update_) {
|
||||
const auto via_cycles = time_until_video_event_ - via_time_base;
|
||||
via_time_base = HalfCycles(0);
|
||||
via_cycles_outstanding -= via_cycles;
|
||||
|
||||
via_clock_ += via_cycles;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
|
||||
video_.run_for(time_until_video_event_);
|
||||
time_since_video_update_ -= time_until_video_event_;
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
}
|
||||
|
||||
via_clock_ += via_cycles_outstanding;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
}
|
||||
|
||||
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
|
||||
// Its clock and data lines are connected to the VIA.
|
||||
keyboard_clock_ += cycle.length;
|
||||
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
||||
if(keyboard_ticks > HalfCycles(0)) {
|
||||
keyboard_.run_for(keyboard_ticks);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
||||
}
|
||||
|
||||
// Feed mouse inputs within at most 1250 cycles of each other.
|
||||
if(mouse_.has_steps()) {
|
||||
time_since_mouse_update_ += cycle.length;
|
||||
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
|
||||
if(mouse_ticks > HalfCycles(0)) {
|
||||
mouse_.prepare_step();
|
||||
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
|
||||
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
||||
// anything connected.
|
||||
|
||||
// Consider updating the real-time clock.
|
||||
real_time_clock_ += cycle.length;
|
||||
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
|
||||
while(ticks--) {
|
||||
clock_.update();
|
||||
// TODO: leave a delay between toggling the input rather than using this coupled hack.
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
|
||||
}
|
||||
|
||||
// Update interrupt input. TODO: move this into a VIA/etc delegate callback?
|
||||
// Double TODO: does this really cascade like this?
|
||||
if(scc_.get_interrupt_line()) {
|
||||
mc68000_.set_interrupt_level(2);
|
||||
} else if(via_.get_interrupt_line()) {
|
||||
mc68000_.set_interrupt_level(1);
|
||||
} else {
|
||||
mc68000_.set_interrupt_level(0);
|
||||
}
|
||||
|
||||
// A null cycle leaves nothing else to do.
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
|
||||
|
||||
auto word_address = cycle.active_operation_word_address();
|
||||
|
||||
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
||||
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
||||
|
||||
// All code below deals only with reads and writes — cycles in which a
|
||||
// data select is active. So quit now if this is not the active part of
|
||||
// a read or write.
|
||||
if(!cycle.data_select_active()) return delay;
|
||||
|
||||
// Check whether this access maps into the IO area; if so then
|
||||
// apply more complicated decoding logic.
|
||||
if(word_address >= 0x400000) {
|
||||
const int register_address = word_address >> 8;
|
||||
|
||||
switch(word_address & 0x78f000) {
|
||||
case 0x70f000:
|
||||
// VIA accesses are via address 0xefe1fe + register*512,
|
||||
// which at word precision is 0x77f0ff + register*256.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = via_.get_register(register_address);
|
||||
} else {
|
||||
via_.set_register(register_address, cycle.value->halves.low);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x68f000:
|
||||
// The IWM; this is a purely polled device, so can be run on demand.
|
||||
iwm_.flush();
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = iwm_.iwm.read(register_address);
|
||||
} else {
|
||||
iwm_.iwm.write(register_address, cycle.value->halves.low);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x780000:
|
||||
// Phase read.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = phase_ & 7;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x480000: case 0x48f000:
|
||||
case 0x580000: case 0x58f000:
|
||||
// Any word access here adjusts phase.
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
++phase_;
|
||||
} else {
|
||||
if(word_address < 0x500000) {
|
||||
// A0 = 1 => reset; A0 = 0 => read.
|
||||
if(*cycle.address & 1) {
|
||||
scc_.reset();
|
||||
} else {
|
||||
const auto read = scc_.read(int(word_address));
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = read;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(*cycle.address & 1) {
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
scc_.write(int(word_address), 0xff);
|
||||
} else {
|
||||
scc_.write(int(word_address), cycle.value->halves.low);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
printf("Unrecognised read %06x\n", *cycle.address & 0xffffff);
|
||||
cycle.value->halves.low = 0x00;
|
||||
} else {
|
||||
printf("Unrecognised write %06x\n", *cycle.address & 0xffffff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
// Having reached here, this is a RAM or ROM access.
|
||||
|
||||
// When ROM overlay is enabled, the ROM begins at both $000000 and $400000,
|
||||
// and RAM is available at $600000.
|
||||
//
|
||||
// Otherwise RAM is mapped at $000000 and ROM from $400000.
|
||||
uint16_t *memory_base;
|
||||
if(
|
||||
(!ROM_is_overlay_ && word_address < 0x200000) ||
|
||||
(ROM_is_overlay_ && word_address >= 0x300000)
|
||||
) {
|
||||
memory_base = ram_;
|
||||
word_address &= ram_mask_;
|
||||
update_video();
|
||||
} else {
|
||||
memory_base = rom_;
|
||||
word_address &= rom_mask_;
|
||||
|
||||
// Writes to ROM have no effect, and it doesn't mirror above 0x60000.
|
||||
if(!(cycle.operation & Microcycle::Read)) return delay;
|
||||
if(word_address >= 0x300000) {
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
cycle.value->full = 0xffff;
|
||||
} else {
|
||||
cycle.value->halves.low = 0xff;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
}
|
||||
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
|
||||
default:
|
||||
break;
|
||||
|
||||
// Catches the deliberation set of operation to 0 above.
|
||||
case 0: break;
|
||||
|
||||
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
|
||||
// The Macintosh uses autovectored interrupts.
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = memory_base[word_address];
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
memory_base[word_address] = cycle.value->full;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
memory_base[word_address] = uint16_t(
|
||||
(cycle.value->halves.low << cycle.byte_shift()) |
|
||||
(memory_base[word_address] & cycle.untouched_byte_mask())
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Normal memory map:
|
||||
|
||||
000000: RAM
|
||||
400000: ROM
|
||||
9FFFF8+: SCC read operations
|
||||
BFFFF8+: SCC write operations
|
||||
DFE1FF+: IWM
|
||||
EFE1FE+: VIA
|
||||
*/
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
// Flush the video before the audio queue; in a Mac the
|
||||
// video is responsible for providing part of the
|
||||
// audio signal, so the two aren't as distinct as in
|
||||
// most machines.
|
||||
update_video();
|
||||
|
||||
// As above: flush audio after video.
|
||||
via_.flush();
|
||||
audio_.queue.perform();
|
||||
|
||||
// Experimental?
|
||||
iwm_.flush();
|
||||
}
|
||||
|
||||
void set_rom_is_overlay(bool rom_is_overlay) {
|
||||
ROM_is_overlay_ = rom_is_overlay;
|
||||
}
|
||||
|
||||
bool video_is_outputting() {
|
||||
return video_.is_outputting(time_since_video_update_);
|
||||
}
|
||||
|
||||
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
||||
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(media.disks.empty())
|
||||
return false;
|
||||
|
||||
// TODO: shouldn't allow disks to be replaced like this, as the Mac
|
||||
// uses software eject. Will need to expand messaging ability of
|
||||
// insert_media.
|
||||
if(drives_[0].has_disk())
|
||||
drives_[1].set_disk(media.disks[0]);
|
||||
else
|
||||
drives_[0].set_disk(media.disks[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: Keyboard input.
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() override {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
keyboard_.enqueue_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
// TODO: clear all keys.
|
||||
|
||||
private:
|
||||
void update_video() {
|
||||
video_.run_for(time_since_video_update_.flush());
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
}
|
||||
|
||||
Inputs::Mouse &get_mouse() override {
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
struct IWM {
|
||||
IWM(int clock_rate) : iwm(clock_rate) {}
|
||||
|
||||
HalfCycles time_since_update;
|
||||
Apple::IWM iwm;
|
||||
|
||||
void flush() {
|
||||
iwm.run_for(time_since_update.flush_cycles());
|
||||
}
|
||||
};
|
||||
|
||||
class VIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
public:
|
||||
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
|
||||
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {}
|
||||
|
||||
using Port = MOS::MOS6522::Port;
|
||||
using Line = MOS::MOS6522::Line;
|
||||
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
||||
/*
|
||||
Peripheral lines: keyboard data, interrupt configuration.
|
||||
(See p176 [/215])
|
||||
*/
|
||||
switch(port) {
|
||||
case Port::A:
|
||||
/*
|
||||
Port A:
|
||||
b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR)
|
||||
b6: 0 = alternate screen buffer, 1 = main screen buffer
|
||||
b5: floppy disk SEL state control (upper/lower head "among other things")
|
||||
b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map
|
||||
b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer
|
||||
b2–b0: audio output volume
|
||||
*/
|
||||
iwm_.flush();
|
||||
iwm_.iwm.set_select(!!(value & 0x20));
|
||||
|
||||
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
|
||||
machine_.set_rom_is_overlay(!!(value & 0x10));
|
||||
|
||||
audio_.flush();
|
||||
audio_.audio.set_volume(value & 7);
|
||||
break;
|
||||
|
||||
case Port::B:
|
||||
/*
|
||||
Port B:
|
||||
b7: 0 = sound enabled, 1 = sound disabled
|
||||
b6: [input] 0 = video beam in visible portion of line, 1 = outside
|
||||
b5: [input] mouse y2
|
||||
b4: [input] mouse x2
|
||||
b3: [input] 0 = mouse button down, 1 = up
|
||||
b2: 0 = real-time clock enabled, 1 = disabled
|
||||
b1: clock's data-clock line
|
||||
b0: clock's serial data line
|
||||
*/
|
||||
if(value & 0x4) clock_.abort();
|
||||
else clock_.set_input(!!(value & 0x2), !!(value & 0x1));
|
||||
|
||||
audio_.flush();
|
||||
audio_.audio.set_enabled(!(value & 0x80));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(Port port) {
|
||||
switch(port) {
|
||||
case Port::A:
|
||||
// printf("6522 r A\n");
|
||||
return 0x00; // TODO: b7 = SCC wait/request
|
||||
|
||||
case Port::B:
|
||||
return uint8_t(
|
||||
((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) |
|
||||
((mouse_.get_channel(0) & 2) << 3) |
|
||||
((mouse_.get_channel(1) & 2) << 4) |
|
||||
(clock_.get_data() ? 0x02 : 0x00) |
|
||||
(machine_.video_is_outputting() ? 0x00 : 0x40)
|
||||
);
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void set_control_line_output(Port port, Line line, bool value) {
|
||||
/*
|
||||
Keyboard wiring (I believe):
|
||||
CB2 = data (input/output)
|
||||
CB1 = clock (input)
|
||||
|
||||
CA2 is used for receiving RTC interrupts.
|
||||
CA1 is used for receiving vsync.
|
||||
*/
|
||||
if(port == Port::B && line == Line::Two) {
|
||||
keyboard_.set_input(value);
|
||||
}
|
||||
else printf("Unhandled control line output: %c %d\n", port ? 'B' : 'A', int(line));
|
||||
}
|
||||
|
||||
void run_for(HalfCycles duration) {
|
||||
// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
|
||||
// divided-by-two clock the audio works on.
|
||||
audio_.time_since_update += HalfCycles(duration.as_int() * 5);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
audio_.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
ConcreteMachine &machine_;
|
||||
RealTimeClock &clock_;
|
||||
Keyboard &keyboard_;
|
||||
Video &video_;
|
||||
DeferredAudio &audio_;
|
||||
IWM &iwm_;
|
||||
Inputs::QuadratureMouse &mouse_;
|
||||
};
|
||||
|
||||
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
|
||||
|
||||
DriveSpeedAccumulator drive_speed_accumulator_;
|
||||
IWM iwm_;
|
||||
|
||||
DeferredAudio audio_;
|
||||
Video video_;
|
||||
|
||||
RealTimeClock clock_;
|
||||
Keyboard keyboard_;
|
||||
|
||||
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
||||
VIAPortHandler via_port_handler_;
|
||||
|
||||
Zilog::SCC::z8530 scc_;
|
||||
|
||||
HalfCycles via_clock_;
|
||||
HalfCycles real_time_clock_;
|
||||
HalfCycles keyboard_clock_;
|
||||
HalfCycles time_since_video_update_;
|
||||
HalfCycles time_until_video_event_;
|
||||
HalfCycles time_since_iwm_update_;
|
||||
HalfCycles time_since_mouse_update_;
|
||||
|
||||
bool ROM_is_overlay_ = true;
|
||||
int phase_ = 1;
|
||||
|
||||
DoubleDensityDrive drives_[2];
|
||||
Inputs::QuadratureMouse mouse_;
|
||||
|
||||
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
uint32_t ram_mask_ = 0;
|
||||
uint32_t rom_mask_ = 0;
|
||||
uint16_t rom_[64*1024];
|
||||
uint16_t ram_[256*1024];
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
Machine *Machine::Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
auto *const mac_target = dynamic_cast<const Analyser::Static::Macintosh::Target *>(target);
|
||||
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
switch(mac_target->model) {
|
||||
default:
|
||||
case Model::Mac128k: return new ConcreteMachine<Model::Mac128k>(*mac_target, rom_fetcher);
|
||||
case Model::Mac512k: return new ConcreteMachine<Model::Mac512k>(*mac_target, rom_fetcher);
|
||||
case Model::Mac512ke: return new ConcreteMachine<Model::Mac512ke>(*mac_target, rom_fetcher);
|
||||
case Model::MacPlus: return new ConcreteMachine<Model::MacPlus>(*mac_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
30
Machines/Apple/Macintosh/Macintosh.hpp
Normal file
30
Machines/Apple/Macintosh/Macintosh.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// Macintosh.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Macintosh_hpp
|
||||
#define Macintosh_hpp
|
||||
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns a Macintosh.
|
||||
static Machine *Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Macintosh_hpp */
|
166
Machines/Apple/Macintosh/RealTimeClock.hpp
Normal file
166
Machines/Apple/Macintosh/RealTimeClock.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
//
|
||||
// RealTimeClock.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef RealTimeClock_hpp
|
||||
#define RealTimeClock_hpp
|
||||
|
||||
#include "../../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Models the storage component of Apple's real-time clock.
|
||||
|
||||
Since tracking of time is pushed to this class, it is assumed
|
||||
that whomever is translating real time into emulated time
|
||||
will notify the VIA of a potential interrupt.
|
||||
*/
|
||||
class RealTimeClock {
|
||||
public:
|
||||
RealTimeClock() {
|
||||
// TODO: this should persist, if possible, rather than
|
||||
// being randomly initialised.
|
||||
Memory::Fuzz(data_, sizeof(data_));
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances the clock by 1 second.
|
||||
|
||||
The caller should also notify the VIA.
|
||||
*/
|
||||
void update() {
|
||||
for(int c = 0; c < 4; ++c) {
|
||||
++seconds_[c];
|
||||
if(seconds_[c]) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current clock and data inputs to the clock.
|
||||
*/
|
||||
void set_input(bool clock, bool data) {
|
||||
/*
|
||||
Documented commands:
|
||||
|
||||
z0000001 Seconds register 0 (lowest order byte)
|
||||
z0000101 Seconds register 1
|
||||
z0001001 Seconds register 2
|
||||
z0001101 Seconds register 3
|
||||
00110001 Test register (write only)
|
||||
00110101 Write-protect register (write only)
|
||||
z010aa01 RAM addresses 0x10 - 0x13
|
||||
z1aaaa01 RAM addresses 0x00 – 0x0f
|
||||
|
||||
z = 1 => a read; z = 0 => a write.
|
||||
|
||||
The top bit of the write-protect register enables (0) or disables (1)
|
||||
writes to other locations.
|
||||
|
||||
All the documentation says about the test register is to set the top
|
||||
two bits to 0 for normal operation. Abnormal operation is undefined.
|
||||
|
||||
The data line is valid when the clock transitions to level 0.
|
||||
*/
|
||||
|
||||
if(clock && !previous_clock_) {
|
||||
// Shift into the command_ register, no matter what.
|
||||
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
|
||||
result_ <<= 1;
|
||||
|
||||
// Increment phase.
|
||||
++phase_;
|
||||
|
||||
// When phase hits 8, inspect the command.
|
||||
// If it's a read, prepare a result.
|
||||
if(phase_ == 8) {
|
||||
if(command_ & 0x80) {
|
||||
// A read.
|
||||
const auto address = (command_ >> 2) & 0x1f;
|
||||
|
||||
// Begin pessimistically.
|
||||
result_ = 0xff;
|
||||
|
||||
if(address < 4) {
|
||||
result_ = seconds_[address];
|
||||
} else if(address >= 0x10) {
|
||||
result_ = data_[address & 0xf];
|
||||
} else if(address >= 0x8 && address <= 0xb) {
|
||||
result_ = data_[0x10 + (address & 0x3)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If phase hits 16 and this was a read command,
|
||||
// just stop. If it was a write command, do the
|
||||
// actual write.
|
||||
if(phase_ == 16) {
|
||||
if(!(command_ & 0x8000)) {
|
||||
// A write.
|
||||
|
||||
const auto address = (command_ >> 10) & 0x1f;
|
||||
const uint8_t value = uint8_t(command_ & 0xff);
|
||||
|
||||
// First test: is this to the write-protect register?
|
||||
if(address == 0xd) {
|
||||
write_protect_ = value;
|
||||
}
|
||||
|
||||
// No other writing is permitted if the write protect
|
||||
// register won't allow it.
|
||||
if(!(write_protect_ & 0x80)) {
|
||||
if(address < 4) {
|
||||
seconds_[address] = value;
|
||||
} else if(address >= 0x10) {
|
||||
data_[address & 0xf] = value;
|
||||
} else if(address >= 0x8 && address <= 0xb) {
|
||||
data_[0x10 + (address & 0x3)] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A phase of 16 always ends the command, so reset here.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
previous_clock_ = clock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Reads the current data output level from the clock.
|
||||
*/
|
||||
bool get_data() {
|
||||
return !!(result_ & 0x80);
|
||||
}
|
||||
|
||||
/*!
|
||||
Announces that a serial command has been aborted.
|
||||
*/
|
||||
void abort() {
|
||||
result_ = 0;
|
||||
phase_ = 0;
|
||||
command_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data_[0x14];
|
||||
uint8_t seconds_[4];
|
||||
uint8_t write_protect_;
|
||||
|
||||
int phase_ = 0;
|
||||
uint16_t command_;
|
||||
uint8_t result_ = 0;
|
||||
|
||||
bool previous_clock_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* RealTimeClock_hpp */
|
198
Machines/Apple/Macintosh/Video.cpp
Normal file
198
Machines/Apple/Macintosh/Video.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
//
|
||||
// Video.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
|
||||
const HalfCycles line_length(704);
|
||||
const int number_of_lines = 370;
|
||||
const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
|
||||
const int sync_start = 36;
|
||||
const int sync_end = 38;
|
||||
|
||||
}
|
||||
|
||||
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
|
||||
// bottom of page 400:
|
||||
//
|
||||
// "For each scan line, 512 pixels are drawn on the screen ...
|
||||
// The horizontal blanking interval takes the time of an additional 192 pixels"
|
||||
//
|
||||
// And, at the top of 401:
|
||||
//
|
||||
// "The visible portion of a full-screen display consists of 342 horizontal scan lines...
|
||||
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
|
||||
//
|
||||
Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
|
||||
audio_(audio),
|
||||
drive_speed_accumulator_(drive_speed_accumulator),
|
||||
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
|
||||
ram_(ram) {
|
||||
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
|
||||
}
|
||||
|
||||
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
void Video::run_for(HalfCycles duration) {
|
||||
|
||||
// The number of HalfCycles is literally the number of pixel clocks to move through,
|
||||
// since pixel output occurs at twice the processor clock. So divide by 16 to get
|
||||
// the number of fetches.
|
||||
while(duration > HalfCycles(0)) {
|
||||
const auto pixel_start = frame_position_ % line_length;
|
||||
const int line = (frame_position_ / line_length).as_int();
|
||||
|
||||
const auto cycles_left_in_line = std::min(line_length - pixel_start, duration);
|
||||
|
||||
// Line timing, entirely invented as I can find exactly zero words of documentation:
|
||||
//
|
||||
// First 342 lines:
|
||||
//
|
||||
// First 32 words = pixels;
|
||||
// next 5 words = right border;
|
||||
// next 2 words = sync level;
|
||||
// final 5 words = left border.
|
||||
//
|
||||
// Then 12 lines of border, 3 of sync, 11 more of border.
|
||||
|
||||
const int first_word = pixel_start.as_int() >> 4;
|
||||
const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4;
|
||||
|
||||
if(first_word != final_word) {
|
||||
if(line < 342) {
|
||||
// If there are any pixels left to output, do so.
|
||||
if(first_word < 32) {
|
||||
const int final_pixel_word = std::min(final_word, 32);
|
||||
|
||||
if(!first_word) {
|
||||
pixel_buffer_ = crt_.begin_data(512);
|
||||
}
|
||||
|
||||
if(pixel_buffer_) {
|
||||
for(int c = first_word; c < final_pixel_word; ++c) {
|
||||
uint16_t pixels = ram_[video_address_] ^ 0xffff;
|
||||
++video_address_;
|
||||
|
||||
pixel_buffer_[15] = pixels & 0x01;
|
||||
pixel_buffer_[14] = pixels & 0x02;
|
||||
pixel_buffer_[13] = pixels & 0x04;
|
||||
pixel_buffer_[12] = pixels & 0x08;
|
||||
pixel_buffer_[11] = pixels & 0x10;
|
||||
pixel_buffer_[10] = pixels & 0x20;
|
||||
pixel_buffer_[9] = pixels & 0x40;
|
||||
pixel_buffer_[8] = pixels & 0x80;
|
||||
|
||||
pixels >>= 8;
|
||||
pixel_buffer_[7] = pixels & 0x01;
|
||||
pixel_buffer_[6] = pixels & 0x02;
|
||||
pixel_buffer_[5] = pixels & 0x04;
|
||||
pixel_buffer_[4] = pixels & 0x08;
|
||||
pixel_buffer_[3] = pixels & 0x10;
|
||||
pixel_buffer_[2] = pixels & 0x20;
|
||||
pixel_buffer_[1] = pixels & 0x40;
|
||||
pixel_buffer_[0] = pixels & 0x80;
|
||||
|
||||
pixel_buffer_ += 16;
|
||||
}
|
||||
}
|
||||
|
||||
if(final_pixel_word == 32) {
|
||||
crt_.output_data(512);
|
||||
}
|
||||
}
|
||||
|
||||
if(first_word < sync_start && final_word >= sync_start) crt_.output_blank((sync_start - 32) * 16);
|
||||
if(first_word < sync_end && final_word >= sync_end) crt_.output_sync((sync_end - sync_start) * 16);
|
||||
if(final_word == 44) crt_.output_blank((44 - sync_end) * 16);
|
||||
} else if(final_word == 44) {
|
||||
if(line >= 353 && line < 356) {
|
||||
/* Output a sync line. */
|
||||
crt_.output_sync(sync_start * 16);
|
||||
crt_.output_blank((sync_end - sync_start) * 16);
|
||||
crt_.output_sync((44 - sync_end) * 16);
|
||||
} else {
|
||||
/* Output a blank line. */
|
||||
crt_.output_blank(sync_start * 16);
|
||||
crt_.output_sync((sync_end - sync_start) * 16);
|
||||
crt_.output_blank((44 - sync_end) * 16);
|
||||
}
|
||||
}
|
||||
|
||||
// Audio and disk fetches occur "just before video data".
|
||||
if(final_word == 44) {
|
||||
const uint16_t audio_word = ram_[audio_address_];
|
||||
++audio_address_;
|
||||
audio_.audio.post_sample(audio_word >> 8);
|
||||
drive_speed_accumulator_.post_sample(audio_word & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
duration -= cycles_left_in_line;
|
||||
frame_position_ = frame_position_ + cycles_left_in_line;
|
||||
if(frame_position_ == frame_length) {
|
||||
frame_position_ = HalfCycles(0);
|
||||
/*
|
||||
Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers.
|
||||
*/
|
||||
video_address_ = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;
|
||||
|
||||
/*
|
||||
"The main sound buffer is at $1FD00 in a 128K Macintosh, and the alternate buffer is at $1A100;
|
||||
for a 512K Macintosh, add $60000 to these values."
|
||||
*/
|
||||
audio_address_ = (use_alternate_audio_buffer_ ? (0xffffa100 >> 1) : (0xfffffd00 >> 1)) & ram_mask_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Video::vsync() {
|
||||
const int line = (frame_position_ / line_length).as_int();
|
||||
return line >= 353 && line < 356;
|
||||
}
|
||||
|
||||
HalfCycles Video::get_next_sequence_point() {
|
||||
const int line = (frame_position_ / line_length).as_int();
|
||||
if(line >= 353 && line < 356) {
|
||||
// Currently in vsync, so get time until start of line 357,
|
||||
// when vsync will end.
|
||||
return HalfCycles(356) * line_length - frame_position_;
|
||||
} else {
|
||||
// Not currently in vsync, so get time until start of line 353.
|
||||
const auto start_of_vsync = HalfCycles(353) * line_length;
|
||||
if(frame_position_ < start_of_vsync)
|
||||
return start_of_vsync - frame_position_;
|
||||
else
|
||||
return start_of_vsync + HalfCycles(number_of_lines) * line_length - frame_position_;
|
||||
}
|
||||
}
|
||||
|
||||
bool Video::is_outputting(HalfCycles offset) {
|
||||
const auto offset_position = frame_position_ + offset % frame_length;
|
||||
const int column = (offset_position % line_length).as_int() >> 4;
|
||||
const int line = (offset_position / line_length).as_int();
|
||||
return line < 342 && column < 32;
|
||||
}
|
||||
|
||||
void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
||||
use_alternate_screen_buffer_ = use_alternate_screen_buffer;
|
||||
use_alternate_audio_buffer_ = use_alternate_audio_buffer;
|
||||
}
|
||||
|
||||
void Video::set_ram_mask(uint32_t mask) {
|
||||
ram_mask_ = mask;
|
||||
}
|
94
Machines/Apple/Macintosh/Video.hpp
Normal file
94
Machines/Apple/Macintosh/Video.hpp
Normal file
@ -0,0 +1,94 @@
|
||||
//
|
||||
// Video.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Video_hpp
|
||||
#define Video_hpp
|
||||
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "DeferredAudio.hpp"
|
||||
#include "DriveSpeedAccumulator.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image,
|
||||
within a total scanning area of 370 lines, at 352 cycles per line.
|
||||
|
||||
This class also collects audio and 400kb drive-speed data, forwarding those values.
|
||||
*/
|
||||
class Video {
|
||||
public:
|
||||
/*!
|
||||
Constructs an instance of @c Video sourcing its pixel data from @c ram and
|
||||
providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator.
|
||||
*/
|
||||
Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator);
|
||||
|
||||
/*!
|
||||
Sets the target device for video data.
|
||||
*/
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/*!
|
||||
Produces the next @c duration period of pixels.
|
||||
*/
|
||||
void run_for(HalfCycles duration);
|
||||
|
||||
/*!
|
||||
Sets whether the alternate screen and/or audio buffers should be used to source data.
|
||||
*/
|
||||
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer);
|
||||
|
||||
/*!
|
||||
Provides a mask indicating which parts of the generated video and audio/drive addresses are
|
||||
actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff.
|
||||
*/
|
||||
void set_ram_mask(uint32_t);
|
||||
|
||||
/*!
|
||||
@returns @c true if the video is currently outputting a vertical sync, @c false otherwise.
|
||||
*/
|
||||
bool vsync();
|
||||
|
||||
/*
|
||||
@returns @c true if in @c offset half cycles from now, the video will be outputting pixels;
|
||||
@c false otherwise.
|
||||
*/
|
||||
bool is_outputting(HalfCycles offset = HalfCycles(0));
|
||||
|
||||
/*!
|
||||
@returns the amount of time until there is next a transition on the
|
||||
vsync signal.
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
|
||||
private:
|
||||
DeferredAudio &audio_;
|
||||
DriveSpeedAccumulator &drive_speed_accumulator_;
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t *ram_ = nullptr;
|
||||
uint32_t ram_mask_ = 0;
|
||||
|
||||
HalfCycles frame_position_;
|
||||
|
||||
size_t video_address_ = 0;
|
||||
size_t audio_address_ = 0;
|
||||
|
||||
uint8_t *pixel_buffer_ = nullptr;
|
||||
|
||||
bool use_alternate_screen_buffer_ = false;
|
||||
bool use_alternate_audio_buffer_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Video_hpp */
|
@ -11,10 +11,13 @@
|
||||
|
||||
#include "../Configurable/Configurable.hpp"
|
||||
#include "../Activity/Source.hpp"
|
||||
#include "MediaTarget.hpp"
|
||||
|
||||
#include "CRTMachine.hpp"
|
||||
#include "JoystickMachine.hpp"
|
||||
#include "KeyboardMachine.hpp"
|
||||
#include "MediaTarget.hpp"
|
||||
#include "MouseMachine.hpp"
|
||||
|
||||
#include "Utility/Typer.hpp"
|
||||
|
||||
namespace Machine {
|
||||
@ -31,6 +34,7 @@ struct DynamicMachine {
|
||||
virtual CRTMachine::Machine *crt_machine() = 0;
|
||||
virtual JoystickMachine::Machine *joystick_machine() = 0;
|
||||
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
|
||||
virtual MouseMachine::Machine *mouse_machine() = 0;
|
||||
virtual MediaTarget::Machine *media_target() = 0;
|
||||
|
||||
/*!
|
||||
|
@ -34,7 +34,7 @@ enum Key: uint16_t {
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
|
23
Machines/MouseMachine.hpp
Normal file
23
Machines/MouseMachine.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// MouseMachine.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MouseMachine_hpp
|
||||
#define MouseMachine_hpp
|
||||
|
||||
#include "../Inputs/Mouse.hpp"
|
||||
|
||||
namespace MouseMachine {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual Inputs::Mouse &get_mouse() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MouseMachine_hpp */
|
@ -170,7 +170,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
/*!
|
||||
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
|
||||
*/
|
||||
inline void run_for(const Cycles cycles) {
|
||||
inline void run_for(const HalfCycles cycles) {
|
||||
cycles_since_ay_update_ += cycles;
|
||||
}
|
||||
|
||||
@ -182,11 +182,11 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush());
|
||||
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush_cycles());
|
||||
}
|
||||
bool ay_bdir_ = false;
|
||||
bool ay_bc1_ = false;
|
||||
Cycles cycles_since_ay_update_;
|
||||
HalfCycles cycles_since_ay_update_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
GI::AY38910::AY38910 &ay8910_;
|
||||
@ -434,7 +434,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
|
||||
via_.run_for(Cycles(1));
|
||||
via_port_handler_.run_for(Cycles(1));
|
||||
tape_player_.run_for(Cycles(1));
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
@ -456,7 +455,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_port_handler_.flush();
|
||||
via_.flush();
|
||||
flush_diskii();
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,8 @@
|
||||
#include "MachineForTarget.hpp"
|
||||
|
||||
#include "../AmstradCPC/AmstradCPC.hpp"
|
||||
#include "../AppleII/AppleII.hpp"
|
||||
#include "../Apple/AppleII/AppleII.hpp"
|
||||
#include "../Apple/Macintosh/Macintosh.hpp"
|
||||
#include "../Atari2600/Atari2600.hpp"
|
||||
#include "../ColecoVision/ColecoVision.hpp"
|
||||
#include "../Commodore/Vic-20/Vic20.hpp"
|
||||
@ -33,14 +34,15 @@ namespace {
|
||||
#define Bind(m) BindD(m, m)
|
||||
switch(target->machine) {
|
||||
Bind(AmstradCPC)
|
||||
Bind(AppleII)
|
||||
BindD(Apple::II, AppleII)
|
||||
BindD(Apple::Macintosh, Macintosh)
|
||||
Bind(Atari2600)
|
||||
BindD(Coleco::Vision, ColecoVision)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
Bind(Electron)
|
||||
BindD(Sega::MasterSystem, MasterSystem)
|
||||
Bind(MSX)
|
||||
Bind(Oric)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
BindD(Sega::MasterSystem, MasterSystem)
|
||||
Bind(ZX8081)
|
||||
|
||||
default:
|
||||
@ -103,6 +105,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
||||
case Analyser::Machine::Atari2600: return "Atari2600";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Electron";
|
||||
case Analyser::Machine::Macintosh: return "Macintosh";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
case Analyser::Machine::Oric: return "Oric";
|
||||
case Analyser::Machine::Vic20: return "Vic20";
|
||||
@ -119,6 +122,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
case Analyser::Machine::Atari2600: return "Atari 2600";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Acorn Electron";
|
||||
case Analyser::Machine::Macintosh: return "Apple Macintosh";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
case Analyser::Machine::Oric: return "Oric";
|
||||
case Analyser::Machine::Vic20: return "Vic 20";
|
||||
@ -132,7 +136,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
|
||||
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
|
||||
|
@ -23,6 +23,10 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::Fuzz(uint16_t *buffer, std::size_t size) {
|
||||
Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
|
||||
Fuzz(buffer.data(), buffer.size());
|
||||
}
|
||||
|
@ -18,7 +18,10 @@ namespace Memory {
|
||||
/// Stores @c size random bytes from @c buffer onwards.
|
||||
void Fuzz(uint8_t *buffer, std::size_t size);
|
||||
|
||||
// Replaces all existing vector contents with random bytes.
|
||||
/// Stores @c size random 16-bit words from @c buffer onwards.
|
||||
void Fuzz(uint16_t *buffer, std::size_t size);
|
||||
|
||||
/// Replaces all existing vector contents with random bytes.
|
||||
void Fuzz(std::vector<uint8_t> &buffer);
|
||||
|
||||
}
|
||||
|
15
Machines/Utility/MemoryPacker.cpp
Normal file
15
Machines/Utility/MemoryPacker.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// MemoryPacker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MemoryPacker.hpp"
|
||||
|
||||
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) {
|
||||
for(std::size_t c = 0; c < source.size(); c += 2) {
|
||||
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);
|
||||
}
|
||||
}
|
24
Machines/Utility/MemoryPacker.hpp
Normal file
24
Machines/Utility/MemoryPacker.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// MemoryPacker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/05/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MemoryPacker_hpp
|
||||
#define MemoryPacker_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Memory {
|
||||
|
||||
/*!
|
||||
Copies the bytes from @c source into @c target, interpreting them
|
||||
as big-endian 16-bit data.
|
||||
*/
|
||||
void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target);
|
||||
|
||||
}
|
||||
#endif /* MemoryPacker_hpp */
|
@ -45,6 +45,10 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
|
||||
return get<KeyboardMachine::Machine>();
|
||||
}
|
||||
|
||||
MouseMachine::Machine *mouse_machine() override {
|
||||
return get<MouseMachine::Machine>();
|
||||
}
|
||||
|
||||
Configurable::Device *configurable_device() override {
|
||||
return get<Configurable::Device>();
|
||||
}
|
||||
|
@ -93,7 +93,6 @@
|
||||
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
||||
4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; };
|
||||
4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
|
||||
4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */; };
|
||||
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; };
|
||||
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||
@ -111,6 +110,7 @@
|
||||
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
|
||||
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
|
||||
4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B08A56720D72BEF0016CE5A /* Activity.xib */; };
|
||||
4B0C956E22A7109A0015A8F6 /* PlusTooBIN.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0C956C22A7109A0015A8F6 /* PlusTooBIN.cpp */; };
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
|
||||
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; };
|
||||
@ -131,10 +131,6 @@
|
||||
4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */; };
|
||||
4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; };
|
||||
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; };
|
||||
4B15AA0D2082C799005E6C8D /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0A2082C799005E6C8D /* Video.cpp */; };
|
||||
4B15AA0E2082C799005E6C8D /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0A2082C799005E6C8D /* Video.cpp */; };
|
||||
4B15AA0F2082C799005E6C8D /* AppleII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0C2082C799005E6C8D /* AppleII.cpp */; };
|
||||
4B15AA102082C799005E6C8D /* AppleII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15AA0C2082C799005E6C8D /* AppleII.cpp */; };
|
||||
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */; };
|
||||
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */; };
|
||||
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B88B9202E2EC100B67DFF /* MultiKeyboardMachine.cpp */; };
|
||||
@ -240,11 +236,22 @@
|
||||
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
|
||||
4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
|
||||
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; };
|
||||
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE0050227CE8CA000CA200 /* AppleII.cpp */; };
|
||||
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE004E227CE8CA000CA200 /* DiskIICard.cpp */; };
|
||||
4B8318B222D3E53C006DB630 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE004D227CE8CA000CA200 /* Video.cpp */; };
|
||||
4B8318B322D3E540006DB630 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; };
|
||||
4B8318B422D3E546006DB630 /* DriveSpeedAccumulator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */; };
|
||||
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE0058227CFFCA000CA200 /* Macintosh.cpp */; };
|
||||
4B8318B722D3E54D006DB630 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||
4B8318B822D3E566006DB630 /* IWM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1498227FC0EA00133682 /* IWM.cpp */; };
|
||||
4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; };
|
||||
4B8318BA22D3E579006DB630 /* MacintoshIMG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */; };
|
||||
4B8318BB22D3E57C006DB630 /* PlusTooBIN.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0C956C22A7109A0015A8F6 /* PlusTooBIN.cpp */; };
|
||||
4B8318BC22D3E588006DB630 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; };
|
||||
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; };
|
||||
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
|
||||
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334851F5DA3780097E338 /* 6502Storage.cpp */; };
|
||||
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; };
|
||||
4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */; };
|
||||
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
||||
4B85322D227793CB00F26553 /* etos192uk.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322C227793CA00F26553 /* etos192uk.trace.txt.gz */; };
|
||||
4B85322F2277ABDE00F26553 /* tos100.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */; };
|
||||
@ -299,15 +306,22 @@
|
||||
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; };
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */; };
|
||||
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */; };
|
||||
4B90467422C6FADD000E2074 /* 68000BitwiseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */; };
|
||||
4B90467622C6FD6E000E2074 /* 68000ArithmeticTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */; };
|
||||
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
|
||||
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; };
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
|
||||
4B9378E422A199C600973513 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; };
|
||||
4B97ADC822C6FD9B00A22A41 /* 68000ArithmeticTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */; };
|
||||
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; };
|
||||
4B98A05F1FFAD62400ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; };
|
||||
4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */; };
|
||||
4B98A1CE1FFADEC500ADF63B /* MSX ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */; };
|
||||
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
|
||||
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; };
|
||||
4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */; };
|
||||
4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9D0C4C22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm */; };
|
||||
4B9D0C4F22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */; };
|
||||
4B9F11C92272375400701480 /* qltrace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11C82272375400701480 /* qltrace.txt.gz */; };
|
||||
4B9F11CA2272433900701480 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B9F11CC22729B3600701480 /* OPCLOGR2.BIN in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */; };
|
||||
@ -325,6 +339,8 @@
|
||||
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
|
||||
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
|
||||
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
|
||||
4BB244D522AABAF600BE20E5 /* z8530.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB244D322AABAF500BE20E5 /* z8530.cpp */; };
|
||||
4BB244D622AABAF600BE20E5 /* z8530.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB244D322AABAF500BE20E5 /* z8530.cpp */; };
|
||||
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
|
||||
4BB298F21B587D8400A49093 /* adca in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E61B587D8300A49093 /* adca */; };
|
||||
4BB298F31B587D8400A49093 /* adcax in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E71B587D8300A49093 /* adcax */; };
|
||||
@ -591,6 +607,10 @@
|
||||
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
|
||||
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
|
||||
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; };
|
||||
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */; };
|
||||
4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */; };
|
||||
4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */; };
|
||||
4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */; };
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
|
||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
|
||||
@ -607,8 +627,7 @@
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
|
||||
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
||||
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
|
||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||
@ -618,6 +637,14 @@
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
|
||||
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */; };
|
||||
4BCD634922D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD634722D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp */; };
|
||||
4BCD634A22D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCD634722D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp */; };
|
||||
4BCE0051227CE8CA000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE004D227CE8CA000CA200 /* Video.cpp */; };
|
||||
4BCE0052227CE8CA000CA200 /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE004E227CE8CA000CA200 /* DiskIICard.cpp */; };
|
||||
4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE0050227CE8CA000CA200 /* AppleII.cpp */; };
|
||||
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE0058227CFFCA000CA200 /* Macintosh.cpp */; };
|
||||
4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; };
|
||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
||||
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||
@ -644,6 +671,7 @@
|
||||
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
|
||||
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; };
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
|
||||
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
|
||||
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */; };
|
||||
@ -654,6 +682,9 @@
|
||||
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
|
||||
4BEE149A227FC0EA00133682 /* IWM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1498227FC0EA00133682 /* IWM.cpp */; };
|
||||
4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */; };
|
||||
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */; };
|
||||
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
@ -708,6 +739,7 @@
|
||||
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
|
||||
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
|
||||
4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ScanTarget.cpp; path = ../../Outputs/ScanTarget.cpp; sourceTree = "<group>"; };
|
||||
4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
|
||||
@ -721,6 +753,8 @@
|
||||
4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
|
||||
4B08A2791EE3957B008B7065 /* TestMachine+ForSubclassEyesOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestMachine+ForSubclassEyesOnly.h"; sourceTree = "<group>"; };
|
||||
4B08A56820D72BEF0016CE5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Activity.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B0C956C22A7109A0015A8F6 /* PlusTooBIN.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PlusTooBIN.cpp; sourceTree = "<group>"; };
|
||||
4B0C956D22A7109A0015A8F6 /* PlusTooBIN.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PlusTooBIN.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = "<group>"; };
|
||||
@ -749,10 +783,6 @@
|
||||
4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = "<group>"; };
|
||||
4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = AppleII/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4B15A9FB208249BB005E6C8D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = AppleII/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4B15AA092082C799005E6C8D /* AppleII.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleII.hpp; sourceTree = "<group>"; };
|
||||
4B15AA0A2082C799005E6C8D /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
|
||||
4B15AA0B2082C799005E6C8D /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||
4B15AA0C2082C799005E6C8D /* AppleII.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppleII.cpp; sourceTree = "<group>"; };
|
||||
4B1667F61FFF1E2400A16032 /* Konami.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Konami.hpp; path = MSX/Cartridges/Konami.hpp; sourceTree = "<group>"; };
|
||||
4B1667F91FFF215E00A16032 /* ASCII16kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII16kb.hpp; path = MSX/Cartridges/ASCII16kb.hpp; sourceTree = "<group>"; };
|
||||
4B1667FA1FFF215E00A16032 /* ASCII8kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII8kb.hpp; path = MSX/Cartridges/ASCII8kb.hpp; sourceTree = "<group>"; };
|
||||
@ -961,7 +991,6 @@
|
||||
4B8334851F5DA3780097E338 /* 6502Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Storage.cpp; sourceTree = "<group>"; };
|
||||
4B8334871F5DB8410097E338 /* 6522Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 6522Implementation.hpp; path = Implementation/6522Implementation.hpp; sourceTree = "<group>"; };
|
||||
4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IRQDelegatePortHandler.cpp; path = Implementation/IRQDelegatePortHandler.cpp; sourceTree = "<group>"; };
|
||||
4B83348B1F5DB99C0097E338 /* 6522Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 6522Base.cpp; path = Implementation/6522Base.cpp; sourceTree = "<group>"; };
|
||||
4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 6522Storage.hpp; path = Implementation/6522Storage.hpp; sourceTree = "<group>"; };
|
||||
4B8334911F5E24FF0097E338 /* C1540Base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = C1540Base.hpp; path = Implementation/C1540Base.hpp; sourceTree = "<group>"; };
|
||||
4B8334941F5E25B60097E338 /* C1540.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = C1540.cpp; path = Implementation/C1540.cpp; sourceTree = "<group>"; };
|
||||
@ -1034,17 +1063,29 @@
|
||||
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePanel.swift; sourceTree = "<group>"; };
|
||||
4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+DataResource.h"; sourceTree = "<group>"; };
|
||||
4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+DataResource.m"; sourceTree = "<group>"; };
|
||||
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestRunner68000.hpp; sourceTree = "<group>"; };
|
||||
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BitwiseTests.mm; sourceTree = "<group>"; };
|
||||
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ArithmeticTests.mm; sourceTree = "<group>"; };
|
||||
4B92294222B04A3D00A1458F /* MouseMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseMachine.hpp; sourceTree = "<group>"; };
|
||||
4B92294422B04ACB00A1458F /* Mouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mouse.hpp; sourceTree = "<group>"; };
|
||||
4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = "<group>"; };
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; };
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
|
||||
4B9378E222A199C600973513 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = "<group>"; };
|
||||
4B9378E322A199C600973513 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
|
||||
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = "<group>"; };
|
||||
4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = "<group>"; };
|
||||
4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = 68000ArithmeticTests.mm; path = "/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm"; sourceTree = "<absolute>"; };
|
||||
4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CSROMFetcher.hpp; sourceTree = "<group>"; };
|
||||
4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSROMFetcher.mm; sourceTree = "<group>"; };
|
||||
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSXStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; };
|
||||
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; };
|
||||
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
|
||||
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BCDTests.mm; sourceTree = "<group>"; };
|
||||
4B9D0C4C22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ControlFlowTests.mm; sourceTree = "<group>"; };
|
||||
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000RollShiftTests.mm; sourceTree = "<group>"; };
|
||||
4B9F11C82272375400701480 /* qltrace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = qltrace.txt.gz; sourceTree = "<group>"; };
|
||||
4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = OPCLOGR2.BIN; path = "68000 Coverage/OPCLOGR2.BIN"; sourceTree = "<group>"; };
|
||||
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
@ -1065,6 +1106,8 @@
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = "<group>"; };
|
||||
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
|
||||
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
|
||||
4BB244D322AABAF500BE20E5 /* z8530.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = z8530.cpp; sourceTree = "<group>"; };
|
||||
4BB244D422AABAF500BE20E5 /* z8530.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = z8530.hpp; sourceTree = "<group>"; };
|
||||
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
|
||||
4BB297E61B587D8300A49093 /* adca */ = {isa = PBXFileReference; lastKnownFileType = file; path = adca; sourceTree = "<group>"; };
|
||||
4BB297E71B587D8300A49093 /* adcax */ = {isa = PBXFileReference; lastKnownFileType = file; path = adcax; sourceTree = "<group>"; };
|
||||
@ -1331,6 +1374,13 @@
|
||||
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
|
||||
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = "<group>"; };
|
||||
4BB4BFAA22A300710069048D /* DeferredAudio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredAudio.hpp; sourceTree = "<group>"; };
|
||||
4BB4BFAB22A33D710069048D /* DriveSpeedAccumulator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DriveSpeedAccumulator.hpp; sourceTree = "<group>"; };
|
||||
4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DriveSpeedAccumulator.cpp; sourceTree = "<group>"; };
|
||||
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MacintoshIMG.cpp; sourceTree = "<group>"; };
|
||||
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MacintoshIMG.hpp; sourceTree = "<group>"; };
|
||||
4BB4BFB722A4372E0069048D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
|
||||
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
|
||||
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
|
||||
@ -1363,9 +1413,7 @@
|
||||
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; };
|
||||
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
|
||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
|
||||
4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; };
|
||||
4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; };
|
||||
4BC39567208EE6CF0044766B /* DiskIICard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskIICard.hpp; sourceTree = "<group>"; };
|
||||
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000MoveTests.mm; sourceTree = "<group>"; };
|
||||
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
|
||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
||||
@ -1380,9 +1428,25 @@
|
||||
4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreROM.cpp; path = Encodings/CommodoreROM.cpp; sourceTree = "<group>"; };
|
||||
4BCA6CC71D9DD9F000C2D7B2 /* CommodoreROM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreROM.hpp; path = Encodings/CommodoreROM.hpp; sourceTree = "<group>"; };
|
||||
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
|
||||
4BCD634722D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MacintoshDoubleDensityDrive.cpp; sourceTree = "<group>"; };
|
||||
4BCD634822D6756400F567F1 /* MacintoshDoubleDensityDrive.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MacintoshDoubleDensityDrive.hpp; sourceTree = "<group>"; };
|
||||
4BCE004A227CE8CA000CA200 /* AppleII.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleII.hpp; sourceTree = "<group>"; };
|
||||
4BCE004B227CE8CA000CA200 /* Card.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; };
|
||||
4BCE004C227CE8CA000CA200 /* DiskIICard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskIICard.hpp; sourceTree = "<group>"; };
|
||||
4BCE004D227CE8CA000CA200 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
|
||||
4BCE004E227CE8CA000CA200 /* DiskIICard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; };
|
||||
4BCE004F227CE8CA000CA200 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||
4BCE0050227CE8CA000CA200 /* AppleII.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppleII.cpp; sourceTree = "<group>"; };
|
||||
4BCE0058227CFFCA000CA200 /* Macintosh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Macintosh.cpp; sourceTree = "<group>"; };
|
||||
4BCE0059227CFFCA000CA200 /* Macintosh.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Macintosh.hpp; sourceTree = "<group>"; };
|
||||
4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryPacker.cpp; sourceTree = "<group>"; };
|
||||
4BCE005C227D30CC000CA200 /* MemoryPacker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryPacker.hpp; sourceTree = "<group>"; };
|
||||
4BCE005E227D39AB000CA200 /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
|
||||
4BCE005F227D39AB000CA200 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = "<group>"; };
|
||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
|
||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = "<group>"; };
|
||||
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
|
||||
4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
|
||||
4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
|
||||
@ -1409,6 +1473,7 @@
|
||||
4BD67DCE209BF27B00AB2146 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = "<group>"; };
|
||||
4BD67DCF209BF27B00AB2146 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
|
||||
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
|
||||
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
|
||||
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
|
||||
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
|
||||
4BE3231220532443006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
@ -1421,6 +1486,7 @@
|
||||
4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
|
||||
4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = "<group>"; };
|
||||
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
|
||||
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; };
|
||||
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
|
||||
@ -1452,6 +1518,9 @@
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6E1D72496600532C7B /* PRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PRG.hpp; sourceTree = "<group>"; };
|
||||
4BEE1498227FC0EA00133682 /* IWM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IWM.cpp; sourceTree = "<group>"; };
|
||||
4BEE1499227FC0EA00133682 /* IWM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IWM.hpp; sourceTree = "<group>"; };
|
||||
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacGCRTests.mm; sourceTree = "<group>"; };
|
||||
4BEEE6BC20DC72EA003723BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/CompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
|
||||
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
|
||||
@ -1610,20 +1679,6 @@
|
||||
name = AppleII;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B15AA082082C799005E6C8D /* AppleII */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B15AA0C2082C799005E6C8D /* AppleII.cpp */,
|
||||
4BC39566208EE6CF0044766B /* DiskIICard.cpp */,
|
||||
4B15AA0A2082C799005E6C8D /* Video.cpp */,
|
||||
4B15AA092082C799005E6C8D /* AppleII.hpp */,
|
||||
4BC39565208EDFCE0044766B /* Card.hpp */,
|
||||
4BC39567208EE6CF0044766B /* DiskIICard.hpp */,
|
||||
4B15AA0B2082C799005E6C8D /* Video.hpp */,
|
||||
);
|
||||
path = AppleII;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1667F81FFF1E2900A16032 /* Cartridges */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1711,13 +1766,15 @@
|
||||
children = (
|
||||
4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */,
|
||||
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */,
|
||||
4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */,
|
||||
4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */,
|
||||
4B2B3A471F9B8FA70062DABF /* Typer.cpp */,
|
||||
4B055ABF1FAE98000060FFFF /* MachineForTarget.hpp */,
|
||||
4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */,
|
||||
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */,
|
||||
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */,
|
||||
4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */,
|
||||
4BCE005C227D30CC000CA200 /* MemoryPacker.hpp */,
|
||||
4B17B58A20A8A9D9007CCA8F /* StringSerialiser.hpp */,
|
||||
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */,
|
||||
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */,
|
||||
);
|
||||
path = Utility;
|
||||
sourceTree = "<group>";
|
||||
@ -1764,6 +1821,10 @@
|
||||
children = (
|
||||
4B302182208A550100773308 /* DiskII.hpp */,
|
||||
4B302183208A550100773308 /* DiskII.cpp */,
|
||||
4BEE1498227FC0EA00133682 /* IWM.cpp */,
|
||||
4BEE1499227FC0EA00133682 /* IWM.hpp */,
|
||||
4BCD634722D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp */,
|
||||
4BCD634822D6756400F567F1 /* MacintoshDoubleDensityDrive.hpp */,
|
||||
);
|
||||
path = DiskII;
|
||||
sourceTree = "<group>";
|
||||
@ -1935,6 +1996,7 @@
|
||||
4B0333AD2094081A0050B93D /* AppleDSK.cpp */,
|
||||
4B45188F1F75FD1B00926311 /* CPCDSK.cpp */,
|
||||
4B4518911F75FD1B00926311 /* D64.cpp */,
|
||||
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */,
|
||||
4BAF2B4C2004580C00480230 /* DMK.cpp */,
|
||||
4B4518931F75FD1B00926311 /* G64.cpp */,
|
||||
4B4518951F75FD1B00926311 /* HFE.cpp */,
|
||||
@ -1942,12 +2004,14 @@
|
||||
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */,
|
||||
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */,
|
||||
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
|
||||
4B0C956C22A7109A0015A8F6 /* PlusTooBIN.cpp */,
|
||||
4B4518991F75FD1B00926311 /* SSD.cpp */,
|
||||
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
|
||||
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
|
||||
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
|
||||
4B4518901F75FD1B00926311 /* CPCDSK.hpp */,
|
||||
4B4518921F75FD1B00926311 /* D64.hpp */,
|
||||
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */,
|
||||
4BAF2B4D2004580C00480230 /* DMK.hpp */,
|
||||
4B4518941F75FD1B00926311 /* G64.hpp */,
|
||||
4B4518961F75FD1B00926311 /* HFE.hpp */,
|
||||
@ -1955,6 +2019,7 @@
|
||||
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */,
|
||||
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */,
|
||||
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
|
||||
4B0C956D22A7109A0015A8F6 /* PlusTooBIN.hpp */,
|
||||
4B45189A1F75FD1B00926311 /* SSD.hpp */,
|
||||
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */,
|
||||
4BFDD7891F7F2DB4008579B9 /* Utility */,
|
||||
@ -2244,7 +2309,6 @@
|
||||
4B8334881F5DB8470097E338 /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B83348B1F5DB99C0097E338 /* 6522Base.cpp */,
|
||||
4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */,
|
||||
4B8334871F5DB8410097E338 /* 6522Implementation.hpp */,
|
||||
4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */,
|
||||
@ -2274,8 +2338,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B86E2591F8C628F006FAA45 /* Keyboard.cpp */,
|
||||
4B86E25A1F8C628F006FAA45 /* Keyboard.hpp */,
|
||||
4B70412A1F92C2A700735E45 /* Joystick.hpp */,
|
||||
4B86E25A1F8C628F006FAA45 /* Keyboard.hpp */,
|
||||
4B92294422B04ACB00A1458F /* Mouse.hpp */,
|
||||
4B92294922B064FD00A1458F /* QuadratureMouse */,
|
||||
);
|
||||
name = Inputs;
|
||||
path = ../../Inputs;
|
||||
@ -2349,6 +2415,7 @@
|
||||
4B8944FB201967B4007DE474 /* Commodore */,
|
||||
4B894507201967B4007DE474 /* Disassembler */,
|
||||
4BD67DC8209BE4D600AB2146 /* DiskII */,
|
||||
4BB4BFB622A4372E0069048D /* Macintosh */,
|
||||
4B89450F201967B4007DE474 /* MSX */,
|
||||
4B8944F6201967B4007DE474 /* Oric */,
|
||||
4B7F1894215486A100388727 /* Sega */,
|
||||
@ -2467,6 +2534,14 @@
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B92294922B064FD00A1458F /* QuadratureMouse */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */,
|
||||
);
|
||||
path = QuadratureMouse;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B9F11C72272375400701480 /* QL Startup */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2500,6 +2575,15 @@
|
||||
path = SN76489;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB244D222AABAF500BE20E5 /* 8530 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB244D322AABAF500BE20E5 /* z8530.cpp */,
|
||||
4BB244D422AABAF500BE20E5 /* z8530.hpp */,
|
||||
);
|
||||
path = 8530;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2772,6 +2856,16 @@
|
||||
path = "Wolfgang Lorenz 6502 test suite";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB4BFB622A4372E0069048D /* Macintosh */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB4BFB722A4372E0069048D /* StaticAnalyser.hpp */,
|
||||
4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */,
|
||||
4B04B65622A58CB40006AB58 /* Target.hpp */,
|
||||
);
|
||||
path = Macintosh;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2858,10 +2952,19 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B85322922778E4200F26553 /* Comparative68000.hpp */,
|
||||
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
|
||||
4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */,
|
||||
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
|
||||
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
|
||||
4B9D0C4C22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm */,
|
||||
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */,
|
||||
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */,
|
||||
4BD388872239E198002D14B5 /* 68000Tests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */,
|
||||
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */,
|
||||
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */,
|
||||
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */,
|
||||
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
@ -2887,6 +2990,7 @@
|
||||
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */,
|
||||
4B3BA0C41D318B44005DD7A7 /* Bridges */,
|
||||
4B1414631B588A1100E04248 /* Test Binaries */,
|
||||
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
|
||||
);
|
||||
path = "Clock SignalTests";
|
||||
sourceTree = "<group>";
|
||||
@ -2909,9 +3013,10 @@
|
||||
4B7041271F92C26900735E45 /* JoystickMachine.hpp */,
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */,
|
||||
4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */,
|
||||
4B92294222B04A3D00A1458F /* MouseMachine.hpp */,
|
||||
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
|
||||
4B38F3491F2EC12000D9235D /* AmstradCPC */,
|
||||
4B15AA082082C799005E6C8D /* AppleII */,
|
||||
4BCE0048227CE8CA000CA200 /* Apple */,
|
||||
4B2E2D961C3A06EC00138695 /* Atari2600 */,
|
||||
4B7A90E22041097C008514A2 /* ColecoVision */,
|
||||
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
|
||||
@ -2997,8 +3102,6 @@
|
||||
4BC9DF4A1D04691600F44158 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B302181208A550100773308 /* DiskII */,
|
||||
4B595FAA2086DFBA0083CAA8 /* AudioToggle */,
|
||||
4BD468F81D8DF4290084958B /* 1770 */,
|
||||
4BC9DF4B1D04691600F44158 /* 6522 */,
|
||||
4B1E85791D174DEC001EF87D /* 6532 */,
|
||||
@ -3006,8 +3109,11 @@
|
||||
4BE845221F2FF7F400A5EA22 /* 6845 */,
|
||||
4BD9137C1F3115AC009BCF85 /* 8255 */,
|
||||
4BBC951F1F368D87008F4C34 /* 8272 */,
|
||||
4BB244D222AABAF500BE20E5 /* 8530 */,
|
||||
4B0E04F71FC9F2C800F43484 /* 9918 */,
|
||||
4B595FAA2086DFBA0083CAA8 /* AudioToggle */,
|
||||
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
|
||||
4B302181208A550100773308 /* DiskII */,
|
||||
4B4B1A39200198C900A0F866 /* KonamiSCC */,
|
||||
4BB0A6582044FD3000FB3688 /* SN76489 */,
|
||||
);
|
||||
@ -3042,6 +3148,47 @@
|
||||
name = Encodings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCE0048227CE8CA000CA200 /* Apple */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BCE0049227CE8CA000CA200 /* AppleII */,
|
||||
4BCE0057227CFFCA000CA200 /* Macintosh */,
|
||||
);
|
||||
path = Apple;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCE0049227CE8CA000CA200 /* AppleII */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BCE0050227CE8CA000CA200 /* AppleII.cpp */,
|
||||
4BCE004E227CE8CA000CA200 /* DiskIICard.cpp */,
|
||||
4BCE004D227CE8CA000CA200 /* Video.cpp */,
|
||||
4BCE004A227CE8CA000CA200 /* AppleII.hpp */,
|
||||
4BCE004B227CE8CA000CA200 /* Card.hpp */,
|
||||
4BCE004C227CE8CA000CA200 /* DiskIICard.hpp */,
|
||||
4BCE004F227CE8CA000CA200 /* Video.hpp */,
|
||||
);
|
||||
path = AppleII;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCE0057227CFFCA000CA200 /* Macintosh */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B9378E222A199C600973513 /* Audio.cpp */,
|
||||
4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */,
|
||||
4BCE0058227CFFCA000CA200 /* Macintosh.cpp */,
|
||||
4BCE005E227D39AB000CA200 /* Video.cpp */,
|
||||
4B9378E322A199C600973513 /* Audio.hpp */,
|
||||
4BB4BFAA22A300710069048D /* DeferredAudio.hpp */,
|
||||
4BB4BFAB22A33D710069048D /* DriveSpeedAccumulator.hpp */,
|
||||
4BDB3D8522833321002D3CEE /* Keyboard.hpp */,
|
||||
4BCE0059227CFFCA000CA200 /* Macintosh.hpp */,
|
||||
4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */,
|
||||
4BCE005F227D39AB000CA200 /* Video.hpp */,
|
||||
);
|
||||
path = Macintosh;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -3221,11 +3368,11 @@
|
||||
4BF660691F281573002CB053 /* ClockReceiver */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */,
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
@ -3720,10 +3867,12 @@
|
||||
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
|
||||
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
|
||||
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
|
||||
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
|
||||
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
|
||||
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
|
||||
4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */,
|
||||
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
|
||||
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */,
|
||||
4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */,
|
||||
4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
||||
@ -3751,15 +3900,18 @@
|
||||
4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */,
|
||||
4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */,
|
||||
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */,
|
||||
4BCD634A22D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
|
||||
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 */,
|
||||
4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */,
|
||||
4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
|
||||
4B1B88C1202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */,
|
||||
4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */,
|
||||
4B8318B322D3E540006DB630 /* Audio.cpp in Sources */,
|
||||
4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */,
|
||||
4B89452B201967B4007DE474 /* File.cpp in Sources */,
|
||||
4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */,
|
||||
@ -3778,13 +3930,16 @@
|
||||
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */,
|
||||
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
|
||||
4B8318BB22D3E57C006DB630 /* PlusTooBIN.cpp in Sources */,
|
||||
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
|
||||
4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */,
|
||||
4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */,
|
||||
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */,
|
||||
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */,
|
||||
4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */,
|
||||
4B055AC51FAE9AEE0060FFFF /* Atari2600.cpp in Sources */,
|
||||
4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */,
|
||||
4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */,
|
||||
4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */,
|
||||
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
|
||||
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */,
|
||||
@ -3793,16 +3948,15 @@
|
||||
4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */,
|
||||
4B8318B222D3E53C006DB630 /* Video.cpp in Sources */,
|
||||
4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */,
|
||||
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B15AA0E2082C799005E6C8D /* Video.cpp in Sources */,
|
||||
4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
|
||||
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
|
||||
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */,
|
||||
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */,
|
||||
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
|
||||
4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */,
|
||||
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
|
||||
4B15AA102082C799005E6C8D /* AppleII.cpp in Sources */,
|
||||
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
|
||||
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
|
||||
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
|
||||
@ -3816,6 +3970,7 @@
|
||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
|
||||
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||
4B8318BC22D3E588006DB630 /* DisplayMetrics.cpp in Sources */,
|
||||
4B1B88BD202E3D3D00B67DFF /* MultiMachine.cpp in Sources */,
|
||||
4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
|
||||
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
|
||||
@ -3823,6 +3978,7 @@
|
||||
4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */,
|
||||
4B89451F201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
|
||||
4B8318B422D3E546006DB630 /* DriveSpeedAccumulator.cpp in Sources */,
|
||||
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
|
||||
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
|
||||
4B055A911FAE85B50060FFFF /* Cartridge.cpp in Sources */,
|
||||
@ -3834,12 +3990,14 @@
|
||||
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
|
||||
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
|
||||
4B894527201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BB244D622AABAF600BE20E5 /* z8530.cpp in Sources */,
|
||||
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */,
|
||||
4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */,
|
||||
4BD424E82193B5830097291A /* Rectangle.cpp in Sources */,
|
||||
4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */,
|
||||
4B8318BA22D3E579006DB630 /* MacintoshIMG.cpp in Sources */,
|
||||
4B8318B822D3E566006DB630 /* IWM.cpp in Sources */,
|
||||
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||
4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */,
|
||||
4B894535201967B4007DE474 /* AddressMapper.cpp in Sources */,
|
||||
4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */,
|
||||
4B055A921FAE85B50060FFFF /* PRG.cpp in Sources */,
|
||||
@ -3876,6 +4034,7 @@
|
||||
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
|
||||
4B9378E422A199C600973513 /* Audio.cpp in Sources */,
|
||||
4B89451E201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */,
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||
@ -3894,8 +4053,10 @@
|
||||
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
|
||||
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */,
|
||||
4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */,
|
||||
4BCE0052227CE8CA000CA200 /* DiskIICard.cpp in Sources */,
|
||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */,
|
||||
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */,
|
||||
4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */,
|
||||
4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
|
||||
4B894538201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */,
|
||||
@ -3906,7 +4067,7 @@
|
||||
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
|
||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */,
|
||||
4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */,
|
||||
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */,
|
||||
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
|
||||
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
|
||||
@ -3918,6 +4079,7 @@
|
||||
4BD424E52193B5830097291A /* Shader.cpp in Sources */,
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
||||
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
|
||||
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||
@ -3932,6 +4094,7 @@
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */,
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
|
||||
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */,
|
||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
|
||||
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
|
||||
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
|
||||
@ -3951,12 +4114,14 @@
|
||||
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */,
|
||||
4B89451A201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
|
||||
4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */,
|
||||
4BEE149A227FC0EA00133682 /* IWM.cpp in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4B86E25B1F8C628F006FAA45 /* Keyboard.cpp in Sources */,
|
||||
4B4518851F75E91A00926311 /* DiskController.cpp in Sources */,
|
||||
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */,
|
||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */,
|
||||
4BCD634922D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
|
||||
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */,
|
||||
4B89452A201967B4007DE474 /* File.cpp in Sources */,
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||
@ -3971,6 +4136,7 @@
|
||||
4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */,
|
||||
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */,
|
||||
4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
|
||||
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
|
||||
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
|
||||
4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
@ -3989,7 +4155,6 @@
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4B15AA0F2082C799005E6C8D /* AppleII.cpp in Sources */,
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
|
||||
@ -4002,7 +4167,9 @@
|
||||
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */,
|
||||
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */,
|
||||
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
|
||||
4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */,
|
||||
4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */,
|
||||
4BCE0051227CE8CA000CA200 /* Video.cpp in Sources */,
|
||||
4B0C956E22A7109A0015A8F6 /* PlusTooBIN.cpp in Sources */,
|
||||
4B894536201967B4007DE474 /* Z80.cpp in Sources */,
|
||||
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
|
||||
4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */,
|
||||
@ -4032,14 +4199,15 @@
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
||||
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
|
||||
4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */,
|
||||
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */,
|
||||
4BD424E72193B5830097291A /* Rectangle.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 */,
|
||||
4B894534201967B4007DE474 /* AddressMapper.cpp in Sources */,
|
||||
4B15AA0D2082C799005E6C8D /* Video.cpp in Sources */,
|
||||
4B1B88C8202E469300B67DFF /* MultiJoystickMachine.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -4054,9 +4222,12 @@
|
||||
4B98A05F1FFAD62400ADF63B /* CSROMFetcher.mm in Sources */,
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
|
||||
4B90467422C6FADD000E2074 /* 68000BitwiseTests.mm in Sources */,
|
||||
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */,
|
||||
4B90467622C6FD6E000E2074 /* 68000ArithmeticTests.mm in Sources */,
|
||||
4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */,
|
||||
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */,
|
||||
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */,
|
||||
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */,
|
||||
4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */,
|
||||
4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */,
|
||||
@ -4065,6 +4236,8 @@
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
|
||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */,
|
||||
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */,
|
||||
4B97ADC822C6FD9B00A22A41 /* 68000ArithmeticTests.mm in Sources */,
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
|
||||
4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */,
|
||||
4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */,
|
||||
@ -4074,17 +4247,22 @@
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
||||
4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */,
|
||||
4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */,
|
||||
4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */,
|
||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,
|
||||
4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */,
|
||||
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
|
||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
|
||||
4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */,
|
||||
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */,
|
||||
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */,
|
||||
4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */,
|
||||
4B322E011F5A2990004EB04C /* Z80AllRAM.cpp in Sources */,
|
||||
4B9D0C4F22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -4236,6 +4414,7 @@
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_OPTIMIZATION_LEVEL = 3;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
@ -4404,6 +4583,7 @@
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
);
|
||||
GCC_OPTIMIZATION_LEVEL = 3;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "NDEBUG=1";
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
@ -4451,6 +4631,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = fast;
|
||||
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";
|
||||
|
@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "self:Clock Signal.xcodeproj">
|
||||
</FileRef>
|
||||
|
@ -72,13 +72,13 @@ class MachineDocument:
|
||||
|
||||
fileprivate func setupMachineOutput() {
|
||||
if let machine = self.machine, let openGLView = self.openGLView {
|
||||
// establish the output aspect ratio and audio
|
||||
// Establish the output aspect ratio and audio.
|
||||
let aspectRatio = self.aspectRatio()
|
||||
openGLView.perform(glContext: {
|
||||
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
|
||||
})
|
||||
|
||||
// attach an options panel if one is available
|
||||
// Attach an options panel if one is available.
|
||||
if let optionsPanelNibName = self.optionsPanelNibName {
|
||||
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
|
||||
self.optionsPanel.machine = machine
|
||||
@ -89,19 +89,22 @@ class MachineDocument:
|
||||
machine.delegate = self
|
||||
self.bestEffortUpdater = CSBestEffortUpdater()
|
||||
|
||||
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate
|
||||
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate.
|
||||
openGLView.delegate = self
|
||||
openGLView.responderDelegate = self
|
||||
|
||||
// If this machine has a mouse, enable mouse capture.
|
||||
openGLView.shouldCaptureMouse = machine.hasMouse
|
||||
|
||||
setupAudioQueueClockRate()
|
||||
|
||||
// bring OpenGL view-holding window on top of the options panel and show the content
|
||||
// Bring OpenGL view-holding window on top of the options panel and show the content.
|
||||
openGLView.isHidden = false
|
||||
openGLView.window!.makeKeyAndOrderFront(self)
|
||||
openGLView.window!.makeFirstResponder(openGLView)
|
||||
|
||||
// start accepting best effort updates
|
||||
// Start accepting best effort updates.
|
||||
self.bestEffortUpdater!.delegate = self
|
||||
}
|
||||
}
|
||||
@ -252,6 +255,7 @@ class MachineDocument:
|
||||
machine.clearAllKeys()
|
||||
machine.joystickManager = nil
|
||||
}
|
||||
self.openGLView.releaseMouse()
|
||||
}
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
@ -281,6 +285,24 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
func mouseMoved(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.addMouseMotionX(event.deltaX, y: event.deltaY)
|
||||
}
|
||||
}
|
||||
|
||||
func mouseUp(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setMouseButton(Int32(event.buttonNumber), isPressed: false)
|
||||
}
|
||||
}
|
||||
|
||||
func mouseDown(_ event: NSEvent) {
|
||||
if let machine = self.machine {
|
||||
machine.setMouseButton(Int32(event.buttonNumber), isPressed: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: New machine creation
|
||||
@IBOutlet var machinePicker: MachinePicker?
|
||||
@IBOutlet var machinePickerPanel: NSWindow?
|
||||
|
@ -14,42 +14,42 @@
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Atari 2600 Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rom</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>chip.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ROM Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@ -57,110 +57,110 @@
|
||||
<string>uef</string>
|
||||
<string>uef.gz</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC UEF Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>prg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Program</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>g64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Disk</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>d64</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore 1540/1 Disk</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@ -171,44 +171,44 @@
|
||||
<string>adl</string>
|
||||
<string>adm</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dsk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@ -216,22 +216,22 @@
|
||||
<string>o</string>
|
||||
<string>80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX80 Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@ -240,240 +240,240 @@
|
||||
<string>81</string>
|
||||
<string>p81</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX81 Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>csw</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tzx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cdt</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amstrad CPC Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>hfe</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HxC Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cas</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dmk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tsx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>col</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ColecoVision Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sms</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Master System Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sg</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cartridge.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>SG1000 Cartridge</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
@ -483,22 +483,44 @@
|
||||
<string>do</string>
|
||||
<string>po</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy525.png</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Apple II Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>img</string>
|
||||
<string>hfv</string>
|
||||
<string>image</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>floppy35</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>DiskCopy 4.2 Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
@ -61,6 +61,9 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
|
||||
- (void)clearAllKeys;
|
||||
|
||||
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed;
|
||||
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY;
|
||||
|
||||
@property (nonatomic, strong, nullable) CSAudioQueue *audioQueue;
|
||||
@property (nonatomic, readonly, nonnull) CSOpenGLView *view;
|
||||
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
|
||||
@ -81,6 +84,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
// Input control.
|
||||
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||
@property (nonatomic, readonly) BOOL hasMouse;
|
||||
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
||||
@property (nonatomic, nullable) CSJoystickManager *joystickManager;
|
||||
|
||||
|
@ -410,14 +410,14 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
|
||||
- (void)clearAllKeys {
|
||||
auto keyboard_machine = _machine->keyboard_machine();
|
||||
const auto keyboard_machine = _machine->keyboard_machine();
|
||||
if(keyboard_machine) {
|
||||
@synchronized(self) {
|
||||
keyboard_machine->get_keyboard().reset_all_keys();
|
||||
}
|
||||
}
|
||||
|
||||
auto joystick_machine = _machine->joystick_machine();
|
||||
const auto joystick_machine = _machine->joystick_machine();
|
||||
if(joystick_machine) {
|
||||
@synchronized(self) {
|
||||
for(auto &joystick : joystick_machine->get_joysticks()) {
|
||||
@ -425,6 +425,31 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().reset_all_buttons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed {
|
||||
auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY {
|
||||
auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Options
|
||||
@ -540,6 +565,10 @@ struct ActivityObserver: public Activity::Observer {
|
||||
return !!_machine->joystick_machine();
|
||||
}
|
||||
|
||||
- (BOOL)hasMouse {
|
||||
return !!_machine->mouse_machine();
|
||||
}
|
||||
|
||||
- (BOOL)hasExclusiveKeyboard {
|
||||
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
|
||||
}
|
||||
|
@ -29,6 +29,13 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
|
||||
CSMachineCPCModel6128
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) {
|
||||
CSMachineMacintoshModel128k,
|
||||
CSMachineMacintoshModel512k,
|
||||
CSMachineMacintoshModel512ke,
|
||||
CSMachineMacintoshModelPlus,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineOricModel) {
|
||||
CSMachineOricModelOric1,
|
||||
CSMachineOricModelOricAtmos,
|
||||
@ -69,6 +76,7 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
|
||||
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController;
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
|
||||
|
||||
@property(nonatomic, readonly) NSString *optionsPanelNibName;
|
||||
@property(nonatomic, readonly) NSString *displayName;
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "../../../../../Analyser/Static/AmstradCPC/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/AppleII/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/MSX/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Oric/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/ZX8081/Target.hpp"
|
||||
@ -188,7 +189,27 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
target->machine = Analyser::Machine::Macintosh;
|
||||
|
||||
using Model = Target::Model;
|
||||
switch(model) {
|
||||
default:
|
||||
case CSMachineMacintoshModel128k: target->model = Model::Mac128k; break;
|
||||
case CSMachineMacintoshModel512k: target->model = Model::Mac512k; break;
|
||||
case CSMachineMacintoshModel512ke: target->model = Model::Mac512ke; break;
|
||||
case CSMachineMacintoshModelPlus: target->model = Model::MacPlus; break;
|
||||
}
|
||||
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)optionsPanelNibName {
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@ -17,14 +17,14 @@
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="600" height="205"/>
|
||||
<rect key="contentRect" x="196" y="240" width="650" height="205"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="205"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="650" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
|
||||
<rect key="frame" x="499" y="13" width="87" height="32"/>
|
||||
<rect key="frame" x="549" y="13" width="87" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MnM-xo-4Qa">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -37,7 +37,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JQy-Cj-AOK">
|
||||
<rect key="frame" x="418" y="13" width="82" height="32"/>
|
||||
<rect key="frame" x="468" y="13" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sub-rB-Req">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -59,7 +59,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<tabView translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
|
||||
<rect key="frame" x="13" y="51" width="574" height="140"/>
|
||||
<rect key="frame" x="13" y="51" width="624" height="140"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
|
||||
@ -130,7 +130,7 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
@ -168,18 +168,18 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
<view key="view" id="SRc-2D-95G">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="654" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
|
||||
<rect key="frame" x="15" y="74" width="164" height="18"/>
|
||||
<rect key="frame" x="15" y="75" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
|
||||
<rect key="frame" x="15" y="54" width="228" height="18"/>
|
||||
<rect key="frame" x="15" y="55" width="228" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -197,9 +197,15 @@ Gw
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
|
||||
<view key="view" id="7Yf-vi-Q0W">
|
||||
<rect key="frame" x="10" y="33" width="654" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
|
||||
<view key="view" id="mWD-An-tR7">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="654" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
|
||||
@ -233,6 +239,7 @@ Gw
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="17" id="0Oc-n7-gaM"/>
|
||||
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="6" id="LBt-4m-GDc"/>
|
||||
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="3" id="bcb-ZZ-VpQ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="17" id="l8P-UW-8ig"/>
|
||||
@ -246,7 +253,7 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
|
||||
<view key="view" id="sOR-e0-8iZ">
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="654" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
|
||||
@ -388,11 +395,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
|
||||
<view key="view" id="8hL-Vn-Hg0">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
|
||||
<rect key="frame" x="106" y="66" width="115" height="25"/>
|
||||
<rect key="frame" x="106" y="67" width="115" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@ -406,7 +413,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
|
||||
<rect key="frame" x="15" y="71" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="87" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -414,7 +421,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
|
||||
<rect key="frame" x="15" y="47" width="114" height="18"/>
|
||||
<rect key="frame" x="15" y="48" width="114" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -436,11 +443,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
|
||||
<view key="view" id="bmd-gL-gzT">
|
||||
<rect key="frame" x="10" y="33" width="554" height="93"/>
|
||||
<rect key="frame" x="10" y="33" width="554" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
|
||||
<rect key="frame" x="106" y="66" width="115" height="25"/>
|
||||
<rect key="frame" x="106" y="67" width="115" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@ -454,7 +461,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
|
||||
<rect key="frame" x="15" y="71" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="72" width="87" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -157,6 +157,9 @@ class MachinePicker: NSObject {
|
||||
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
|
||||
}
|
||||
|
||||
case "mac":
|
||||
return CSStaticAnalyser(macintoshModel: .model512ke)
|
||||
|
||||
case "msx":
|
||||
let hasDiskDrive = msxHasDiskDriveButton!.state == .on
|
||||
switch msxRegionButton!.selectedItem?.tag {
|
||||
|
@ -63,6 +63,29 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
*/
|
||||
- (void)paste:(nonnull id)sender;
|
||||
|
||||
@optional
|
||||
|
||||
/*!
|
||||
Supplies a mouse moved event to the delegate. This functions only if
|
||||
shouldCaptureMouse is set to YES, in which case the view will ensure it captures
|
||||
the mouse and returns only relative motion
|
||||
(Cf. CGAssociateMouseAndMouseCursorPosition). It will also elide mouseDragged:
|
||||
(and rightMouseDragged:, etc) and mouseMoved: events.
|
||||
*/
|
||||
- (void)mouseMoved:(nonnull NSEvent *)event;
|
||||
|
||||
/*!
|
||||
Supplies a mouse button down event. This elides mouseDown, rightMouseDown and otherMouseDown.
|
||||
@c shouldCaptureMouse must be set to @c YES to receive these events.
|
||||
*/
|
||||
- (void)mouseDown:(nonnull NSEvent *)event;
|
||||
|
||||
/*!
|
||||
Supplies a mouse button up event. This elides mouseUp, rightMouseUp and otherMouseUp.
|
||||
@c shouldCaptureMouse must be set to @c YES to receive these events.
|
||||
*/
|
||||
- (void)mouseUp:(nonnull NSEvent *)event;
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
@ -74,6 +97,8 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
||||
|
||||
/*!
|
||||
Ends the timer tracking time; should be called prior to giving up the last owning reference
|
||||
to ensure that any retain cycles implied by the timer are resolved.
|
||||
@ -89,4 +114,9 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
*/
|
||||
- (void)performWithGLContext:(nonnull dispatch_block_t)action;
|
||||
|
||||
/*!
|
||||
Instructs that the mouse cursor, if currently captured, should be released.
|
||||
*/
|
||||
- (void)releaseMouse;
|
||||
|
||||
@end
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
NSTrackingArea *_mouseTrackingArea;
|
||||
NSTimer *_mouseHideTimer;
|
||||
BOOL _mouseIsCaptured;
|
||||
}
|
||||
|
||||
- (void)prepareOpenGL {
|
||||
@ -148,6 +149,13 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
[self.responderDelegate flagsChanged:theEvent];
|
||||
|
||||
// Release the mouse upon a control + command.
|
||||
if(_mouseIsCaptured &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagControl &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagCommand) {
|
||||
[self releaseMouse];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender {
|
||||
@ -170,6 +178,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
#pragma mark - Mouse hiding
|
||||
|
||||
- (void)setShouldCaptureMouse:(BOOL)shouldCaptureMouse {
|
||||
_shouldCaptureMouse = shouldCaptureMouse;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas {
|
||||
[super updateTrackingAreas];
|
||||
|
||||
@ -185,9 +197,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
[self addTrackingArea:_mouseTrackingArea];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
[super mouseMoved:event];
|
||||
[self scheduleMouseHide];
|
||||
- (void)scheduleMouseHide {
|
||||
if(!self.shouldCaptureMouse) {
|
||||
[_mouseHideTimer invalidate];
|
||||
|
||||
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
|
||||
[NSCursor setHiddenUntilMouseMoves:YES];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event {
|
||||
@ -195,18 +212,119 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
[self scheduleMouseHide];
|
||||
}
|
||||
|
||||
- (void)scheduleMouseHide {
|
||||
[_mouseHideTimer invalidate];
|
||||
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
|
||||
[NSCursor setHiddenUntilMouseMoves:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)event {
|
||||
[super mouseExited:event];
|
||||
|
||||
[_mouseHideTimer invalidate];
|
||||
_mouseHideTimer = nil;
|
||||
}
|
||||
|
||||
- (void)releaseMouse {
|
||||
if(_mouseIsCaptured) {
|
||||
_mouseIsCaptured = NO;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Mouse motion
|
||||
|
||||
- (void)applyMouseMotion:(NSEvent *)event {
|
||||
if(!self.shouldCaptureMouse) {
|
||||
// Mouse capture is off, so don't play games with the cursor, just schedule it to
|
||||
// hide in the near future.
|
||||
[self scheduleMouseHide];
|
||||
} else {
|
||||
if(_mouseIsCaptured) {
|
||||
// Mouse capture is on, so move the cursor back to the middle of the window, and
|
||||
// forward the deltas to the listener.
|
||||
//
|
||||
// TODO: should I really need to invert the y coordinate myself? It suggests I
|
||||
// might have an error in mapping here.
|
||||
const NSPoint windowCentre = [self convertPoint:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5) toView:nil];
|
||||
const NSPoint screenCentre = [self.window convertPointToScreen:windowCentre];
|
||||
const CGRect screenFrame = self.window.screen.frame;
|
||||
CGWarpMouseCursorPosition(NSMakePoint(
|
||||
screenFrame.origin.x + screenCentre.x,
|
||||
screenFrame.origin.y + screenFrame.size.height - screenCentre.y
|
||||
));
|
||||
|
||||
[self.responderDelegate mouseMoved:event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super mouseDragged:event];
|
||||
}
|
||||
|
||||
- (void)rightMouseDragged:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super rightMouseDragged:event];
|
||||
}
|
||||
|
||||
- (void)otherMouseDragged:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super otherMouseDragged:event];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
[self applyMouseMotion:event];
|
||||
[super mouseMoved:event];
|
||||
}
|
||||
|
||||
#pragma mark - Mouse buttons
|
||||
|
||||
- (void)applyButtonDown:(NSEvent *)event {
|
||||
if(self.shouldCaptureMouse) {
|
||||
if(!_mouseIsCaptured) {
|
||||
_mouseIsCaptured = YES;
|
||||
[NSCursor hide];
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
|
||||
// Don't report the first click to the delegate; treat that as merely
|
||||
// an invitation to capture the cursor.
|
||||
return;
|
||||
}
|
||||
|
||||
[self.responderDelegate mouseDown:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyButtonUp:(NSEvent *)event {
|
||||
if(self.shouldCaptureMouse) {
|
||||
[self.responderDelegate mouseUp:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
[self applyButtonDown:event];
|
||||
[super mouseDown:event];
|
||||
}
|
||||
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
[self applyButtonDown:event];
|
||||
[super rightMouseDown:event];
|
||||
}
|
||||
|
||||
- (void)otherMouseDown:(NSEvent *)event {
|
||||
[self applyButtonDown:event];
|
||||
[super otherMouseDown:event];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
[self applyButtonUp:event];
|
||||
[super mouseUp:event];
|
||||
}
|
||||
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
[self applyButtonUp:event];
|
||||
[super rightMouseUp:event];
|
||||
}
|
||||
|
||||
- (void)otherMouseUp:(NSEvent *)event {
|
||||
[self applyButtonUp:event];
|
||||
[super otherMouseUp:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -11,118 +11,244 @@ import Foundation
|
||||
|
||||
class MOS6522Tests: XCTestCase {
|
||||
|
||||
fileprivate func with6522(_ action: (MOS6522Bridge) -> ()) {
|
||||
let bridge = MOS6522Bridge()
|
||||
action(bridge)
|
||||
private var m6522: MOS6522Bridge!
|
||||
|
||||
override func setUp() {
|
||||
m6522 = MOS6522Bridge()
|
||||
}
|
||||
|
||||
// MARK: Timer tests
|
||||
|
||||
func testTimerCount() {
|
||||
with6522 {
|
||||
// set timer 1 to a value of $000a
|
||||
$0.setValue(10, forRegister: 4)
|
||||
$0.setValue(0, forRegister: 5)
|
||||
// set timer 1 to a value of m652200a
|
||||
m6522.setValue(10, forRegister: 4)
|
||||
m6522.setValue(0, forRegister: 5)
|
||||
|
||||
// complete the setting cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the setting cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// run for 5 cycles
|
||||
$0.run(forHalfCycles: 10)
|
||||
// run for 5 cycles
|
||||
m6522.run(forHalfCycles: 10)
|
||||
|
||||
// check that the timer has gone down by 5
|
||||
XCTAssert($0.value(forRegister: 4) == 5, "Low order byte should be 5; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))")
|
||||
}
|
||||
// check that the timer has gone down by 5
|
||||
XCTAssert(m6522.value(forRegister: 4) == 5, "Low order byte should be 5; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))")
|
||||
}
|
||||
|
||||
func testTimerLatches() {
|
||||
with6522 {
|
||||
// set timer 2 to $1020
|
||||
$0.setValue(0x10, forRegister: 8)
|
||||
$0.setValue(0x20, forRegister: 9)
|
||||
// set timer 2 to $1020
|
||||
m6522.setValue(0x10, forRegister: 8)
|
||||
m6522.setValue(0x20, forRegister: 9)
|
||||
|
||||
// change the low-byte latch
|
||||
$0.setValue(0x40, forRegister: 8)
|
||||
// change the low-byte latch
|
||||
m6522.setValue(0x40, forRegister: 8)
|
||||
|
||||
// complete the cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// chek that the new latched value hasn't been copied
|
||||
XCTAssert($0.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 8))")
|
||||
XCTAssert($0.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \($0.value(forRegister: 9))")
|
||||
// chek that the new latched value hasn't been copied
|
||||
XCTAssert(m6522.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 8))")
|
||||
XCTAssert(m6522.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \(m6522.value(forRegister: 9))")
|
||||
|
||||
// write the low-byte latch
|
||||
$0.setValue(0x50, forRegister: 9)
|
||||
// write the low-byte latch
|
||||
m6522.setValue(0x50, forRegister: 9)
|
||||
|
||||
// complete the cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// chek that the latched value has been copied
|
||||
XCTAssert($0.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \($0.value(forRegister: 8))")
|
||||
XCTAssert($0.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \($0.value(forRegister: 9))")
|
||||
}
|
||||
// chek that the latched value has been copied
|
||||
XCTAssert(m6522.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \(m6522.value(forRegister: 8))")
|
||||
XCTAssert(m6522.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \(m6522.value(forRegister: 9))")
|
||||
}
|
||||
|
||||
func testTimerReload() {
|
||||
with6522 {
|
||||
// set timer 1 to a value of $0010, enable repeating mode
|
||||
$0.setValue(16, forRegister: 4)
|
||||
$0.setValue(0, forRegister: 5)
|
||||
$0.setValue(0x40, forRegister: 11)
|
||||
$0.setValue(0x40 | 0x80, forRegister: 14)
|
||||
// set timer 1 to a value of m6522010, enable repeating mode
|
||||
m6522.setValue(16, forRegister: 4)
|
||||
m6522.setValue(0, forRegister: 5)
|
||||
m6522.setValue(0x40, forRegister: 11)
|
||||
m6522.setValue(0x40 | 0x80, forRegister: 14)
|
||||
|
||||
// complete the cycle to set initial values
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle to set initial values
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// run for 16 cycles
|
||||
$0.run(forHalfCycles: 32)
|
||||
// run for 16 cycles
|
||||
m6522.run(forHalfCycles: 32)
|
||||
|
||||
// check that the timer has gone down to 0 but not yet triggered an interrupt
|
||||
XCTAssert($0.value(forRegister: 4) == 0, "Low order byte should be 0; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))")
|
||||
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
|
||||
// check that the timer has gone down to 0 but not yet triggered an interrupt
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0, "Low order byte should be 0; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))")
|
||||
XCTAssert(!m6522.irqLine, "IRQ should not yet be active")
|
||||
|
||||
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
|
||||
$0.run(forHalfCycles: 2)
|
||||
XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))")
|
||||
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
|
||||
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
|
||||
m6522.run(forHalfCycles: 2)
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))")
|
||||
XCTAssert(!m6522.irqLine, "IRQ should not yet be active")
|
||||
|
||||
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
|
||||
$0.run(forHalfCycles: 1)
|
||||
XCTAssert($0.irqLine, "IRQ should be active")
|
||||
XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))")
|
||||
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
|
||||
m6522.run(forHalfCycles: 1)
|
||||
XCTAssert(m6522.irqLine, "IRQ should be active")
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))")
|
||||
|
||||
// ... but that reading the timer cleared the interrupt
|
||||
XCTAssert(!$0.irqLine, "IRQ should be active")
|
||||
// ... but that reading the timer cleared the interrupt
|
||||
XCTAssert(!m6522.irqLine, "IRQ should be active")
|
||||
|
||||
// check that one half-cycles later the timer has reloaded
|
||||
$0.run(forHalfCycles: 1)
|
||||
XCTAssert($0.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 4))")
|
||||
XCTAssert($0.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \($0.value(forRegister: 5))")
|
||||
}
|
||||
// check that one half-cycles later the timer has reloaded
|
||||
m6522.run(forHalfCycles: 1)
|
||||
XCTAssert(m6522.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 4))")
|
||||
XCTAssert(m6522.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \(m6522.value(forRegister: 5))")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Data direction tests
|
||||
func testDataDirection() {
|
||||
with6522 {
|
||||
// set low four bits of register B as output, the top four as input
|
||||
$0.setValue(0xf0, forRegister: 2)
|
||||
// set low four bits of register B as output, the top four as input
|
||||
m6522.setValue(0xf0, forRegister: 2)
|
||||
|
||||
// ask to output 0x8c
|
||||
$0.setValue(0x8c, forRegister: 0)
|
||||
// ask to output 0x8c
|
||||
m6522.setValue(0x8c, forRegister: 0)
|
||||
|
||||
// complete the cycle
|
||||
$0.run(forHalfCycles: 2)
|
||||
// complete the cycle
|
||||
m6522.run(forHalfCycles: 2)
|
||||
|
||||
// set current input as 0xda
|
||||
$0.portBInput = 0xda
|
||||
// set current input as 0xda
|
||||
m6522.portBInput = 0xda
|
||||
|
||||
// test that the result of reading register B is therefore 0x8a
|
||||
XCTAssert($0.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \($0.value(forRegister: 0))")
|
||||
// test that the result of reading register B is therefore 0x8a
|
||||
XCTAssert(m6522.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \(m6522.value(forRegister: 0))")
|
||||
}
|
||||
|
||||
func testShiftDisabled() {
|
||||
/*
|
||||
Mode 0 disables the Shift Register. In this mode the microprocessor can
|
||||
write or read the SR and the SR will shift on each CB1 positive edge
|
||||
shifting in the value on CB2. In this mode the SR Interrupt Flag is
|
||||
disabled (held to a logic 0).
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftInUnderT2() {
|
||||
/*
|
||||
In mode 1, the shifting rate is controlled by the low order 8 bits of T2
|
||||
(Figure 22). Shift pulses are generated on the CB1 pin to control shifting
|
||||
in external devices. The time between transitions of this output clock is a
|
||||
function of the system clock period and the contents of the low order T2
|
||||
latch (N).
|
||||
|
||||
The shifting operation is triggered by the read or write of the SR if the
|
||||
SR flag is set in the IFR. Otherwise the first shift will occur at the next
|
||||
time-out of T2 after a read or write of the SR. Data is shifted first into
|
||||
the low order bit of SR and is then shifted into the next higher order bit
|
||||
of the shift register on the negative-going edge of each clock pulse. The
|
||||
input data should change before the positive-going edge of the CB1 clock
|
||||
pulse. This data is shifted into shift register during the 02 clock cycle
|
||||
following the positive-going edge of the CB1 clock pulse. After 8 CB1 clock
|
||||
pulses, the shift register interrupt flag will set and IRQ will go low.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftInUnderPhase2() {
|
||||
/*
|
||||
In mode 2, the shift rate is a direct function of the system clock
|
||||
frequency (Figure 23). CB1 becomes an output which generates shift pulses
|
||||
for controlling external devices. Timer 2 operates as an independent
|
||||
interval timer and has no effect on SR. The shifting operation is triggered
|
||||
by reading or writing the Shift Register. Data is shifted, first into bit 0
|
||||
and is then shifted into the next higher order bit of the shift register on
|
||||
the trailing edge of each 02 clock pulse. After 8 clock pulses, the shift
|
||||
register interrupt flag will be set, and the output clock pulses on CB1
|
||||
will stop.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftInUnderCB1() {
|
||||
/*
|
||||
In mode 3, external pin CB1 becomes an input (Figure 24). This allows an
|
||||
external device to load the shift register at its own pace. The shift
|
||||
register counter will interrupt the processor each time 8 bits have been
|
||||
shifted in. However the shift register counter does not stop the shifting
|
||||
operation; it acts simply as a pulse counter. Reading or writing the Shift
|
||||
Register resets the Interrupt Flag and initializes the SR counter to count
|
||||
another 8 pulses.
|
||||
|
||||
Note that the data is shifted during the first system clock cycle
|
||||
following the positive-going edge of the CB1 shift pulse. For this reason,
|
||||
data must be held stable during the first full cycle following CB1 going
|
||||
high.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftOutUnderT2FreeRunning() {
|
||||
/*
|
||||
Mode 4 is very similar to mode 5 in which the shifting rate is set by T2.
|
||||
However, in mode 4 the SR Counter does not stop the shifting operation
|
||||
(Figure 25). Since the Shift Register bit 7 (SR7) is recirculated back into
|
||||
bit 0, the 8 bits loaded into the Shift Register will be clocked onto CB2
|
||||
repetitively. In this mode the Shift Register Counter is disabled.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftOutUnderT2() {
|
||||
/*
|
||||
In mode 5, the shift rate is controlled by T2 (as in mode 4). The shifting
|
||||
operation is triggered by the read or write of the SR if the SR flag is set
|
||||
in the IFR (Figure 26). Otherwise the first shift will occur at the next
|
||||
time-out of T2 after a read or write of the SR. However, with each read or
|
||||
write of the shift register the SR Counter is reset and 8 bits are shifted
|
||||
onto CB2. At the same time, 8 shift pulses are generated on CB1 to control
|
||||
shifting in external devices. After the 8 shift pulses, the shifting is
|
||||
disabled, the SR Interrupt Flag is set and CB2 remains at the last data
|
||||
level.
|
||||
*/
|
||||
}
|
||||
|
||||
func testShiftOutUnderPhase2() {
|
||||
/*
|
||||
In mode 6, the shift rate is controlled by the 02 system clock (Figure 27).
|
||||
|
||||
(... and I'm assuming the same behaviour as shift out under control of T2
|
||||
otherwise, based on original context)
|
||||
*/
|
||||
// Set the shift register to a non-zero something.
|
||||
m6522.setValue(0xaa, forRegister: 10)
|
||||
|
||||
// Set shift register mode 6.
|
||||
m6522.setValue(6 << 2, forRegister: 11)
|
||||
|
||||
// Make sure the shift register's interrupt bit is set.
|
||||
m6522.run(forHalfCycles: 16)
|
||||
XCTAssertEqual(m6522.value(forRegister: 13) & 0x04, 0x04)
|
||||
|
||||
// Test that output is now inhibited: CB2 should remain unchanged.
|
||||
let initialOutput = m6522.value(forControlLine: .two, port: .B)
|
||||
for _ in 1...8 {
|
||||
m6522.run(forHalfCycles: 2)
|
||||
XCTAssertEqual(m6522.value(forControlLine: .two, port: .B), initialOutput)
|
||||
}
|
||||
|
||||
// Set a new value to the shift register.
|
||||
m6522.setValue(0x16, forRegister: 10)
|
||||
|
||||
// Test that the new value is shifted out.
|
||||
var output = 0
|
||||
for _ in 1..<8 {
|
||||
m6522.run(forHalfCycles: 2)
|
||||
output = (output << 1) | (m6522.value(forControlLine: .two, port: .B) ? 1 : 0)
|
||||
}
|
||||
XCTAssertEqual(output, 0x16)
|
||||
}
|
||||
|
||||
func testShiftOutUnderCB1() {
|
||||
/*
|
||||
In mode 7, shifting is controlled by pulses applied to the CB1 pin by an
|
||||
external device (Figure 28). The SR counter sets the SR Interrupt Flag each
|
||||
time it counts 8 pulses but it does not disable the shifting function. Each
|
||||
time the microprocessor, writes or reads the shift register, the SR
|
||||
Interrupt Flag is reset and the SR counter is initialized to begin counting
|
||||
the next 8 shift pulses on pin CB1. After 8 shift pulses, the Interrupt
|
||||
Flag is set. The microprocessor can then load the shift register with the
|
||||
next byte of data.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
1580
OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm
Normal file
1580
OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
291
OSBindings/Mac/Clock SignalTests/68000BCDTests.mm
Normal file
291
OSBindings/Mac/Clock SignalTests/68000BCDTests.mm
Normal file
@ -0,0 +1,291 @@
|
||||
//
|
||||
// 68000BCDTests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 29/06/2019.
|
||||
//
|
||||
// Largely ported from the tests of the Portable 68k Emulator.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
@interface M68000BCDTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation M68000BCDTests {
|
||||
std::unique_ptr<RAM68000> _machine;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
_machine.reset(new RAM68000());
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
_machine.reset();
|
||||
}
|
||||
|
||||
// MARK: ABCD
|
||||
|
||||
- (void)testABCD {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x1234567a;
|
||||
state.data[2] = 0xf745ff78;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssertEqual(state.data[1], 0x12345658);
|
||||
XCTAssertEqual(state.data[2], 0xf745ff78);
|
||||
}
|
||||
|
||||
- (void)testABCDZero {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345600;
|
||||
state.data[2] = 0x12345600;
|
||||
state.status = Flag::Zero;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Zero);
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.data[2], 0x12345600);
|
||||
}
|
||||
|
||||
- (void)testABCDNegative {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345645;
|
||||
state.data[2] = 0x12345654;
|
||||
state.status = Flag::Zero;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Negative);
|
||||
XCTAssertEqual(state.data[1], 0x12345699);
|
||||
XCTAssertEqual(state.data[2], 0x12345654);
|
||||
}
|
||||
|
||||
- (void)testABCDWithX {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345645;
|
||||
state.data[2] = 0x12345654;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.data[2], 0x12345654);
|
||||
}
|
||||
|
||||
- (void)testABCDOverflow {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x1234563e;
|
||||
state.data[2] = 0x1234563e;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Overflow);
|
||||
XCTAssertEqual(state.data[1], 0x12345683);
|
||||
XCTAssertEqual(state.data[2], 0x1234563e);
|
||||
}
|
||||
|
||||
- (void)testABCDPredecDifferent {
|
||||
_machine->set_program({
|
||||
0xc30a, // ABCD -(A2), -(A1)
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0xa200;
|
||||
*_machine->ram_at(0x4000) = 0x1900;
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3001;
|
||||
state.address[2] = 0x4001;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssert(state.status & Flag::Extend);
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(state.address[2], 0x4000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||
}
|
||||
|
||||
- (void)testABCDPredecSame {
|
||||
_machine->set_program({
|
||||
0xc309, // ABCD -(A1), -(A1)
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0x19a2;
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3002;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssert(state.status & Flag::Extend);
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
|
||||
}
|
||||
|
||||
// MARK: NBCD
|
||||
|
||||
- (void)performNBCDd1:(uint32_t)d1 ccr:(uint8_t)ccr {
|
||||
_machine->set_program({
|
||||
0x4801 // NBCD D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status |= ccr;
|
||||
state.data[1] = d1;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn {
|
||||
[self performNBCDd1:0x7a ccr:0];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x20);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_extend {
|
||||
[self performNBCDd1:0x1234567a ccr:Flag::Extend | Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x1234561f);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_zero {
|
||||
[self performNBCDd1:0x12345600 ccr:Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_negative {
|
||||
[self performNBCDd1:0x123456ff ccr:Flag::Extend | Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x1234569a);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_XXXw {
|
||||
_machine->set_program({
|
||||
0x4838, 0x3000 // NBCD ($3000).w
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0x0100;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
}
|
||||
|
||||
// MARK: SBCD
|
||||
|
||||
- (void)performSBCDd1:(uint32_t)d1 d2:(uint32_t)d2 ccr:(uint8_t)ccr {
|
||||
_machine->set_program({
|
||||
0x8302 // SBCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status |= ccr;
|
||||
state.data[1] = d1;
|
||||
state.data[2] = d2;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||
XCTAssertEqual(state.data[2], d2);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn {
|
||||
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345611);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_zero {
|
||||
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:Flag::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_negative {
|
||||
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:Flag::Extend];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345688);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_overflow {
|
||||
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:Flag::Extend];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345643);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_PreDec {
|
||||
_machine->set_program({
|
||||
0x830a // SBCD -(A2), -(A1)
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0xa200;
|
||||
*_machine->ram_at(0x4000) = 0x1900;
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3001;
|
||||
state.address[2] = 0x4001;
|
||||
state.status |= Flag::Extend;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative);
|
||||
}
|
||||
|
||||
@end
|
1502
OSBindings/Mac/Clock SignalTests/68000BitwiseTests.mm
Normal file
1502
OSBindings/Mac/Clock SignalTests/68000BitwiseTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
557
OSBindings/Mac/Clock SignalTests/68000ControlFlowTests.mm
Normal file
557
OSBindings/Mac/Clock SignalTests/68000ControlFlowTests.mm
Normal file
@ -0,0 +1,557 @@
|
||||
//
|
||||
// 68000ControlFlowTests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 28/06/2019.
|
||||
//
|
||||
// Largely ported from the tests of the Portable 68k Emulator.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
|
||||
@interface M68000ControlFlowTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation M68000ControlFlowTests {
|
||||
std::unique_ptr<RAM68000> _machine;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
_machine.reset(new RAM68000());
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
_machine.reset();
|
||||
}
|
||||
|
||||
// MARK: Bcc
|
||||
|
||||
- (void)performBccb:(uint16_t)opcode {
|
||||
_machine->set_program({
|
||||
uint16_t(opcode | 6) // Bcc.b +6
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
}
|
||||
|
||||
- (void)performBccw:(uint16_t)opcode {
|
||||
_machine->set_program({
|
||||
opcode, 0x0006 // Bcc.w +6
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
}
|
||||
|
||||
- (void)testBHIb {
|
||||
[self performBccb:0x6200];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
- (void)testBLOb {
|
||||
[self performBccb:0x6500];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 8);
|
||||
}
|
||||
|
||||
- (void)testBHIw {
|
||||
[self performBccw:0x6200];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
- (void)testBLOw {
|
||||
[self performBccw:0x6500];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1004 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 12);
|
||||
}
|
||||
|
||||
// MARK: BRA
|
||||
|
||||
- (void)testBRAb {
|
||||
_machine->set_program({
|
||||
0x6004 // BRA.b +4
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
- (void)testBRAw {
|
||||
_machine->set_program({
|
||||
0x6000, 0x0004 // BRA.b +4
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
// MARK: BSR
|
||||
|
||||
- (void)testBSRw {
|
||||
_machine->set_program({
|
||||
0x6100, 0x0006 // BSR.w $1008
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x3000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
|
||||
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 18);
|
||||
}
|
||||
|
||||
- (void)testBSRb {
|
||||
_machine->set_program({
|
||||
0x6106 // BSR.b $1008
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x3000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
|
||||
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 18);
|
||||
}
|
||||
|
||||
// MARK: CHK
|
||||
|
||||
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
|
||||
_machine->set_program({
|
||||
0x4581 // CHK D1, D2
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = d1;
|
||||
state.data[2] = d2;
|
||||
state.status |= Flag::ConditionCodes;
|
||||
|
||||
_machine->set_initial_stack_pointer(0);
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], d1);
|
||||
XCTAssertEqual(state.data[2], d2);
|
||||
}
|
||||
|
||||
- (void)testCHK_1111v1111 {
|
||||
[self performCHKd1:0x1111 d2:0x1111];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_1111v0000 {
|
||||
[self performCHKd1:0x1111 d2:0x0000];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_8000v8001 {
|
||||
[self performCHKd1:0x8000 d2:0x8001];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0xfffffffa);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
||||
XCTAssertEqual(42, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_8000v8000 {
|
||||
[self performCHKd1:0x8000 d2:0x8000];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative);
|
||||
XCTAssertEqual(44, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: DBcc
|
||||
|
||||
- (void)performDBccTestOpcode:(uint16_t)opcode status:(uint16_t)status d2Outcome:(uint32_t)d2Output {
|
||||
_machine->set_program({
|
||||
opcode, 0x0008 // DBcc D2, +8
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = status;
|
||||
state.data[2] = 1;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[2], d2Output);
|
||||
XCTAssertEqual(state.status, status);
|
||||
}
|
||||
|
||||
- (void)testDBT {
|
||||
[self performDBccTestOpcode:0x50ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBF {
|
||||
[self performDBccTestOpcode:0x51ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBHI {
|
||||
[self performDBccTestOpcode:0x52ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBHI_Carry {
|
||||
[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBHI_Zero {
|
||||
[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLS_CarryOverflow {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry | Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLS_Carry {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLS_Overflow {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCC_Carry {
|
||||
[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCC {
|
||||
[self performDBccTestOpcode:0x54ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBCS {
|
||||
[self performDBccTestOpcode:0x55ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCS_Carry {
|
||||
[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBNE {
|
||||
[self performDBccTestOpcode:0x56ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBNE_Zero {
|
||||
[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBEQ {
|
||||
[self performDBccTestOpcode:0x57ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBEQ_Zero {
|
||||
[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBVC {
|
||||
[self performDBccTestOpcode:0x58ca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBVC_Overflow {
|
||||
[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBVS {
|
||||
[self performDBccTestOpcode:0x59ca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBVS_Overflow {
|
||||
[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBPL {
|
||||
[self performDBccTestOpcode:0x5aca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBPL_Negative {
|
||||
[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBMI {
|
||||
[self performDBccTestOpcode:0x5bca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBMI_Negative {
|
||||
[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative | Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE {
|
||||
[self performDBccTestOpcode:0x5cca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE_Negative {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBGE_Overflow {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT {
|
||||
[self performDBccTestOpcode:0x5dca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT_Negative {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLT_Overflow {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT {
|
||||
[self performDBccTestOpcode:0x5eca status:0 d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT_ZeroNegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero | Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBGT_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Negative | Flag::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT_Zero {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLE {
|
||||
[self performDBccTestOpcode:0x5fca status:0 d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLE_Zero {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLE_Negative {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLE_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
/* Further DBF tests omitted; they seemed to be duplicative, assuming I'm not suffering a failure of comprehension. */
|
||||
|
||||
// MARK: JMP
|
||||
|
||||
- (void)testJMP_A1 {
|
||||
_machine->set_program({
|
||||
0x4ed1 // JMP (A1)
|
||||
});
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3000;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(state.program_counter, 0x3000 + 4);
|
||||
XCTAssertEqual(8, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testJMP_PC {
|
||||
_machine->set_program({
|
||||
0x4efa, 0x000a // JMP PC+a (i.e. to 0x100c)
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: JSR
|
||||
|
||||
- (void)testJSR_PC {
|
||||
_machine->set_program({
|
||||
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
|
||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testJSR_XXXl {
|
||||
_machine->set_program({
|
||||
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
|
||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: NOP
|
||||
|
||||
- (void)testNOP {
|
||||
_machine->set_program({
|
||||
0x4e71 // NOP
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
XCTAssertEqual(4, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: RTR
|
||||
|
||||
- (void)testRTR {
|
||||
_machine->set_program({
|
||||
0x4e77 // RTR
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
*_machine->ram_at(0x2000) = 0x7fff;
|
||||
*_machine->ram_at(0x2002) = 0;
|
||||
*_machine->ram_at(0x2004) = 0xc;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2006);
|
||||
XCTAssertEqual(state.program_counter, 0x10);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes);
|
||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: RTS
|
||||
|
||||
- (void)testRTS {
|
||||
_machine->set_program({
|
||||
0x4e75 // RTS
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
*_machine->ram_at(0x2000) = 0x0000;
|
||||
*_machine->ram_at(0x2002) = 0x000c;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2004);
|
||||
XCTAssertEqual(state.program_counter, 0x000c + 4);
|
||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: TRAP
|
||||
|
||||
- (void)testTRAP {
|
||||
_machine->set_program({
|
||||
0x4e41 // TRAP #1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = 0x700;
|
||||
state.user_stack_pointer = 0x200;
|
||||
state.supervisor_stack_pointer = 0x206;
|
||||
*_machine->ram_at(0x84) = 0xfffe;
|
||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.status, 0x2700);
|
||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
|
||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||
XCTAssertEqual(state.supervisor_stack_pointer, 0x200);
|
||||
XCTAssertEqual(34, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: TRAPV
|
||||
|
||||
- (void)testTRAPV_taken {
|
||||
_machine->set_program({
|
||||
0x4e76 // TRAPV
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x206);
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = 0x702;
|
||||
state.supervisor_stack_pointer = 0x206;
|
||||
*_machine->ram_at(0x1e) = 0xfffe;
|
||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.status, 0x2702);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
|
||||
XCTAssertEqual(34, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testTRAPV_untaken {
|
||||
_machine->set_program({
|
||||
0x4e76 // TRAPV
|
||||
});
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(4, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@end
|
1250
OSBindings/Mac/Clock SignalTests/68000MoveTests.mm
Normal file
1250
OSBindings/Mac/Clock SignalTests/68000MoveTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
1122
OSBindings/Mac/Clock SignalTests/68000RollShiftTests.mm
Normal file
1122
OSBindings/Mac/Clock SignalTests/68000RollShiftTests.mm
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,100 +8,10 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
|
||||
#include "68000.hpp"
|
||||
|
||||
/*!
|
||||
Provides a 68000 with 64kb of RAM in its low address space;
|
||||
/RESET will put the supervisor stack pointer at 0xFFFF and
|
||||
begin execution at 0x0400.
|
||||
*/
|
||||
class RAM68000: public CPU::MC68000::BusHandler {
|
||||
public:
|
||||
RAM68000() : m68000_(*this) {
|
||||
ram_.resize(256*1024);
|
||||
|
||||
// Setup the /RESET vector.
|
||||
ram_[0] = 0;
|
||||
ram_[1] = 0xffff;
|
||||
ram_[2] = 0;
|
||||
ram_[3] = 0x1000;
|
||||
}
|
||||
|
||||
void set_program(const std::vector<uint16_t> &program) {
|
||||
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void will_perform(uint32_t address, uint16_t opcode) {
|
||||
--instructions_remaining_;
|
||||
}
|
||||
|
||||
void run_for_instructions(int count) {
|
||||
instructions_remaining_ = count;
|
||||
while(instructions_remaining_) {
|
||||
run_for(HalfCycles(2));
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
uint16_t *ram_at(uint32_t address) {
|
||||
return &ram_[address >> 1];
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) {
|
||||
const uint32_t word_address = cycle.word_address();
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||
cycle.value->halves.low = 10;
|
||||
} else {
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
||||
default: break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = ram_[word_address];
|
||||
printf("r %04x from %08x \n", cycle.value->full, *cycle.address);
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = ram_[word_address] >> cycle.byte_shift();
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
printf("w %08x of %04x\n", *cycle.address, cycle.value->full);
|
||||
ram_[word_address] = cycle.value->full;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
ram_[word_address] = (cycle.value->full & cycle.byte_mask()) | (ram_[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true>::State get_processor_state() {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
void set_processor_state(const CPU::MC68000::Processor<RAM68000, true>::State &state) {
|
||||
m68000_.set_state(state);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true, true> &processor() {
|
||||
return m68000_;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
|
||||
std::vector<uint16_t> ram_;
|
||||
int instructions_remaining_;
|
||||
};
|
||||
#define LOG_TRACE
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
class CPU::MC68000::ProcessorStorageTests {
|
||||
public:
|
||||
@ -208,7 +118,7 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
_machine.reset();
|
||||
}
|
||||
|
||||
- (void)testABCD {
|
||||
- (void)testABCDLong {
|
||||
for(int d = 0; d < 100; ++d) {
|
||||
_machine.reset(new RAM68000());
|
||||
_machine->set_program({
|
||||
@ -238,17 +148,14 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
|
||||
/* Next instruction would be at 0x406 */
|
||||
});
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.supervisor_stack_pointer = 0x1000;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_initial_stack_pointer(0x1000);
|
||||
|
||||
_machine->run_for_instructions(4);
|
||||
state = _machine->get_processor_state();
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
|
||||
|
||||
const uint16_t *stack_top = _machine->ram_at(state.supervisor_stack_pointer);
|
||||
const uint16_t *const stack_top = _machine->ram_at(state.supervisor_stack_pointer);
|
||||
XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,14 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, MOS6522BridgePort) {
|
||||
MOS6522BridgePortA = 0, MOS6522BridgePortB = 1
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, MOS6522BridgeLine) {
|
||||
MOS6522BridgeLineOne = 0, MOS6522BridgeLineTwo = 1
|
||||
};
|
||||
|
||||
@interface MOS6522Bridge : NSObject
|
||||
|
||||
@property (nonatomic, readonly) BOOL irqLine;
|
||||
@ -16,6 +24,7 @@
|
||||
|
||||
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber;
|
||||
- (uint8_t)valueForRegister:(NSUInteger)registerNumber;
|
||||
- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port;
|
||||
|
||||
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles;
|
||||
|
||||
|
@ -19,7 +19,12 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
bool irq_line;
|
||||
uint8_t port_a_value;
|
||||
uint8_t port_b_value;
|
||||
bool control_line_values[2][2];
|
||||
|
||||
/*
|
||||
All methods below here are to replace those defined by
|
||||
MOS::MOS6522::PortHandler.
|
||||
*/
|
||||
void set_interrupt_status(bool new_status) {
|
||||
irq_line = new_status;
|
||||
}
|
||||
@ -27,6 +32,10 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
return port ? port_b_value : port_a_value;
|
||||
}
|
||||
|
||||
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
|
||||
control_line_values[int(port)][int(line)] = value;
|
||||
}
|
||||
};
|
||||
|
||||
@implementation MOS6522Bridge {
|
||||
@ -75,4 +84,8 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
return _viaPortHandler.port_b_value;
|
||||
}
|
||||
|
||||
- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port {
|
||||
return _viaPortHandler.control_line_values[port][line];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
|
||||
//#define LOG_TRACE
|
||||
|
||||
#include "68000.hpp"
|
||||
#include "Comparative68000.hpp"
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
210
OSBindings/Mac/Clock SignalTests/MacGCRTests.mm
Normal file
210
OSBindings/Mac/Clock SignalTests/MacGCRTests.mm
Normal file
@ -0,0 +1,210 @@
|
||||
//
|
||||
// MacGCRTests.mm
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 15/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "../../../Storage/Disk/Encodings/AppleGCR/Encoder.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Track/TrackSerialiser.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
|
||||
|
||||
@interface MacGCRTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation MacGCRTests {
|
||||
}
|
||||
|
||||
- (void)testSector0Track0Side0Header {
|
||||
const auto header = Storage::Encodings::AppleGCR::Macintosh::header(0x22, 0, 0, false);
|
||||
const std::vector<uint8_t> expected_mark = {
|
||||
0xd5, 0xaa, 0x96,
|
||||
0x96, 0x96, 0x96, 0xd9, 0xd9,
|
||||
0xde, 0xaa, 0xeb
|
||||
};
|
||||
const auto mark_segment = Storage::Disk::PCMSegment(expected_mark);
|
||||
|
||||
XCTAssertEqual(mark_segment.data, header.data);
|
||||
}
|
||||
|
||||
- (void)testSector9Track11Side1Header {
|
||||
const auto header = Storage::Encodings::AppleGCR::Macintosh::header(0x22, 11, 9, true);
|
||||
const std::vector<uint8_t> expected_mark = {
|
||||
0xd5, 0xaa, 0x96,
|
||||
0xad, 0xab, 0xd6, 0xd9, 0x96,
|
||||
0xde, 0xaa, 0xeb
|
||||
};
|
||||
const auto mark_segment = Storage::Disk::PCMSegment(expected_mark);
|
||||
|
||||
XCTAssertEqual(mark_segment.data, header.data);
|
||||
}
|
||||
|
||||
- (void)testData {
|
||||
// This encoding was generated by the first version of my GCR encoder that produced data a Mac
|
||||
// would accept.
|
||||
const std::vector<uint8_t> expected_data = {
|
||||
/* Standard prologue. */
|
||||
0xd5, 0xaa, 0xad,
|
||||
|
||||
/* Sector number. */
|
||||
0x96,
|
||||
|
||||
/* Tags, after GCR encoding. */
|
||||
0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
|
||||
|
||||
/* Sector body. */
|
||||
0xb4, 0xae, 0xa6, 0xe7, 0xf6, 0x96, 0xae, 0xaf, 0xdc, 0xae, 0xcf, 0xce, 0x9d, 0xbe, 0xad, 0xed,
|
||||
0xe6, 0xd7, 0xf4, 0xd9, 0xf4, 0xfd, 0x9f, 0xdb, 0xba, 0xb7, 0xe9, 0x9d, 0x9f, 0xa6, 0xaf, 0xdb,
|
||||
0xe6, 0xeb, 0xea, 0xd9, 0xbe, 0xec, 0xfd, 0xda, 0xb9, 0xba, 0xcd, 0x97, 0xbb, 0xde, 0xfa, 0xf2,
|
||||
0xf5, 0xeb, 0xbf, 0xb7, 0xfb, 0xfd, 0xfb, 0xf2, 0xfd, 0xfc, 0xeb, 0x97, 0xf6, 0xbe, 0xf4, 0xf5,
|
||||
0xb7, 0xaf, 0xcd, 0xcb, 0xdf, 0xb4, 0xfb, 0xfb, 0xdf, 0x9b, 0xcd, 0xcb, 0xe9, 0xde, 0xb5, 0x9b,
|
||||
0xf2, 0xfe, 0xf3, 0xbf, 0xea, 0x9a, 0xa7, 0xad, 0xf6, 0x9b, 0xac, 0xfd, 0xcd, 0xb5, 0xff, 0xda,
|
||||
0xdf, 0xac, 0xaf, 0xeb, 0xa6, 0xfc, 0xf4, 0xf9, 0xee, 0xb7, 0x96, 0xbe, 0xdf, 0xd3, 0xf5, 0xfc,
|
||||
0xef, 0xbb, 0xef, 0xd6, 0xd3, 0xfc, 0xdc, 0x9e, 0xbe, 0xd9, 0xaf, 0x9a, 0xf6, 0xf2, 0xcf, 0xf4,
|
||||
0xbf, 0xf5, 0xae, 0xb4, 0xd3, 0xdf, 0xe9, 0xed, 0xff, 0xed, 0xae, 0xb4, 0xef, 0x97, 0x9b, 0xfc,
|
||||
0x96, 0xdb, 0xb4, 0xbd, 0xac, 0xb5, 0xf2, 0xf5, 0xed, 0xcb, 0xe7, 0xcd, 0xff, 0xb7, 0xff, 0xab,
|
||||
0xd3, 0xde, 0xcb, 0x9e, 0x9b, 0xb2, 0x9e, 0xb6, 0x9a, 0xed, 0x9e, 0x9e, 0xf2, 0xdd, 0x9e, 0x9f,
|
||||
0xcb, 0xaf, 0x9f, 0x9f, 0xdd, 0xcb, 0xa6, 0x9f, 0xac, 0xcb, 0xb6, 0xff, 0xce, 0xfc, 0xfa, 0xa6,
|
||||
0xce, 0xd7, 0xfe, 0xa6, 0xfa, 0xcf, 0xea, 0xbe, 0xb7, 0xfe, 0xf4, 0xdd, 0xb6, 0x97, 0xeb, 0xf5,
|
||||
0xec, 0xae, 0xcf, 0xe6, 0xfe, 0xb9, 0xdf, 0xe7, 0x9b, 0xd3, 0xbc, 0xb7, 0xfa, 0xec, 0xd6, 0xcb,
|
||||
0xbc, 0xb5, 0xec, 0xe9, 0xb3, 0xfa, 0x9e, 0xf9, 0xad, 0xb9, 0xfd, 0xe6, 0xf7, 0xdb, 0xf3, 0xf4,
|
||||
0x9b, 0xbe, 0xfe, 0xfe, 0xdc, 0x9e, 0xfa, 0xff, 0xec, 0xf5, 0xad, 0xfc, 0xdb, 0xf4, 0xde, 0xda,
|
||||
0xcd, 0x96, 0xcd, 0xb7, 0xf5, 0xcd, 0xb6, 0xb4, 0xd7, 0xbd, 0xce, 0xf6, 0x9b, 0xd7, 0xac, 0xdb,
|
||||
0xae, 0xdb, 0xd3, 0xff, 0xea, 0xf4, 0x9a, 0xb5, 0xee, 0xbb, 0xac, 0xf7, 0xf5, 0xb7, 0xa7, 0xee,
|
||||
0xe5, 0xb7, 0xe9, 0x9b, 0xb2, 0xd7, 0xfc, 0xbf, 0xf4, 0xfc, 0xb5, 0xfb, 0xb7, 0xac, 0xbd, 0xb9,
|
||||
0xbb, 0xdd, 0x97, 0xdc, 0xb6, 0xec, 0xf7, 0xa7, 0xff, 0xfc, 0xcd, 0xdc, 0xfb, 0x9e, 0xfa, 0xfc,
|
||||
0xbc, 0xed, 0xaf, 0xbe, 0xb2, 0xdf, 0xb7, 0xae, 0xf5, 0xbf, 0xef, 0xae, 0xf2, 0xb9, 0xbb, 0xfa,
|
||||
0x96, 0xfd, 0xdb, 0xd3, 0xfd, 0xd9, 0xbd, 0xac, 0xae, 0xa7, 0xb2, 0xb7, 0xe5, 0xcb, 0xda, 0xbc,
|
||||
0xf6, 0xbf, 0xde, 0x97, 0xbd, 0xdd, 0xec, 0xe7, 0xce, 0x97, 0xae, 0xcf, 0xd6, 0xdf, 0xfa, 0xcf,
|
||||
0xdf, 0xf2, 0xad, 0xba, 0x97, 0xbc, 0xcd, 0xe9, 0xbe, 0xbb, 0xf7, 0xf5, 0xdd, 0xdc, 0xf9, 0xff,
|
||||
0xbf, 0xe5, 0xb6, 0xfe, 0xa7, 0xbf, 0xb4, 0xd6, 0xce, 0xce, 0xaf, 0xdc, 0xd6, 0xd3, 0xfe, 0xdc,
|
||||
0xf9, 0xbb, 0xe9, 0xb5, 0xbf, 0xdf, 0xdd, 0xa6, 0xac, 0xe5, 0xf6, 0xb5, 0xb5, 0xef, 0xfd, 0xf3,
|
||||
0xff, 0xbe, 0xcf, 0xf3, 0xa6, 0x9e, 0xb2, 0x96, 0xf3, 0xcf, 0xb7, 0xac, 0xf2, 0xb2, 0xd7, 0xd9,
|
||||
0xdc, 0xfc, 0xfc, 0xfc, 0xb3, 0xde, 0x9a, 0xe6, 0xa7, 0xf2, 0xab, 0xb4, 0xf7, 0xab, 0xb4, 0xbe,
|
||||
0xcb, 0x97, 0xe7, 0xad, 0x96, 0xaf, 0xe9, 0xdf, 0xb4, 0xcf, 0x96, 0xbd, 0xd3, 0xfa, 0xde, 0xb3,
|
||||
0xac, 0xb4, 0xe6, 0xbf, 0xa7, 0xdb, 0x96, 0xa7, 0xb7, 0xde, 0xce, 0x9e, 0xeb, 0xe9, 0xd6, 0x9d,
|
||||
0xbc, 0xe7, 0xfe, 0x9e, 0xfa, 0xd3, 0xba, 0xe6, 0xbe, 0xab, 0xcf, 0xd3, 0xfd, 0xdd, 0xf5, 0xad,
|
||||
0xdf, 0xf7, 0xbe, 0xad, 0xfb, 0xfc, 0xbc, 0xcd, 0xe5, 0x9b, 0xab, 0xe7, 0xea, 0xb2, 0xdb, 0xbb,
|
||||
0xbc, 0xda, 0xe6, 0xa6, 0xdb, 0xea, 0xb7, 0x9b, 0xed, 0xaf, 0xb2, 0xf5, 0xd9, 0xb9, 0xbe, 0xdb,
|
||||
0x9f, 0xfd, 0xf3, 0xef, 0xff, 0xae, 0xfa, 0xf9, 0xba, 0xff, 0xe9, 0xf3, 0xf4, 0xcf, 0xe6, 0xbf,
|
||||
0xdb, 0xae, 0xad, 0xb3, 0xad, 0xaf, 0xe9, 0xbf, 0xa7, 0xaf, 0xbd, 0xdf, 0xae, 0xb4, 0xbb, 0xdd,
|
||||
0xce, 0xcf, 0xb7, 0xfd, 0xec, 0xce, 0xbe, 0xde, 0xf2, 0xbd, 0xe7, 0x9d, 0xdf, 0xce, 0xac, 0xbf,
|
||||
0xd3, 0xfe, 0xe7, 0xdd, 0xfc, 0xb6, 0xe7, 0xb4, 0xfa, 0xbd, 0xef, 0xae, 0xa7, 0xdf, 0xbd, 0xf7,
|
||||
0xa7, 0xdd, 0xbe, 0x9e, 0x9a, 0xea, 0x97, 0xdb, 0xb2, 0x9b, 0xa7, 0xd7, 0x96, 0xf5, 0xb4, 0xbf,
|
||||
0xbc, 0xd7, 0x9a, 0xcb, 0xce, 0xb9, 0xb3, 0xb5, 0xaf, 0xb9, 0xfa, 0xf2, 0xbf, 0xb3, 0xdc, 0xdb,
|
||||
0xad, 0xd7, 0xbe, 0x9d, 0xea, 0xda, 0xb2, 0x9e, 0xea, 0xbd, 0xbe, 0xb7, 0xcb, 0xac, 0x9d, 0xb7,
|
||||
0xe7, 0xee, 0xd3, 0xb9, 0xcf, 0xce, 0xf9, 0x96, 0xf4, 0xbd, 0xab, 0xb5, 0xed, 0xe5, 0xea, 0xf4,
|
||||
0xfb, 0xd7, 0xf7, 0xbe, 0xf4, 0xfb, 0xb4, 0xbf, 0xee, 0xac, 0xde, 0xbd, 0xdc, 0xb6, 0xcf, 0xab,
|
||||
0xb7, 0xaf, 0xf7, 0xfb, 0xb3, 0xb3, 0xee, 0xe9, 0xcd, 0xb7, 0xd9, 0xfc, 0xd9, 0x9d, 0xef,
|
||||
|
||||
/* Standard epilogue. */
|
||||
0xde, 0xaa, 0xeb
|
||||
};
|
||||
|
||||
// This is the first sector, taken from a random disk image from the internet.
|
||||
const uint8_t source_data[524] = {
|
||||
/* Tags. */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
/* Sector body. */
|
||||
0x4c, 0x4b, 0x60, 0x00, 0x00, 0x86, 0x00, 0x12, 0x00, 0x00, 0x06, 0x53, 0x79, 0x73, 0x74, 0x65,
|
||||
0x6d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x06, 0x46, 0x69, 0x6e, 0x64, 0x65,
|
||||
0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x07, 0x4d, 0x61, 0x63, 0x73, 0x62,
|
||||
0x75, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0c, 0x44, 0x69, 0x73, 0x61, 0x73,
|
||||
0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x20, 0x20, 0x20, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74,
|
||||
0x55, 0x70, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x20, 0x06, 0x46, 0x69, 0x6e, 0x64, 0x65,
|
||||
0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0e, 0x43, 0x6c, 0x69, 0x70, 0x62,
|
||||
0x6f, 0x61, 0x72, 0x64, 0x20, 0x46, 0x69, 0x6c, 0x65, 0x20, 0x00, 0x0a, 0x00, 0x14, 0x00, 0x00,
|
||||
0x43, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x22, 0x38, 0x01, 0x08, 0x48, 0x41,
|
||||
0x0c, 0x41, 0x00, 0x04, 0x6e, 0x2c, 0x72, 0x00, 0x50, 0xf9, 0x00, 0x01, 0xff, 0xf0, 0x42, 0xb9,
|
||||
0x00, 0x03, 0xff, 0xf0, 0x4a, 0xb9, 0x00, 0x01, 0xff, 0xf0, 0x67, 0x16, 0x70, 0x02, 0x48, 0x40,
|
||||
0xd1, 0xb8, 0x01, 0x08, 0xd1, 0xb8, 0x08, 0x24, 0xd1, 0xb8, 0x02, 0x66, 0xd1, 0xb8, 0x01, 0x0c,
|
||||
0x72, 0x04, 0x20, 0x78, 0x02, 0xa6, 0x0c, 0x41, 0x00, 0x08, 0x6f, 0x02, 0x72, 0x08, 0xd1, 0xfb,
|
||||
0x10, 0xae, 0x2f, 0x08, 0x70, 0x07, 0x41, 0xf8, 0x0a, 0xb8, 0x42, 0x98, 0x51, 0xc8, 0xff, 0xfc,
|
||||
0x34, 0x3a, 0xff, 0x9a, 0x70, 0x16, 0xc0, 0xc2, 0x22, 0x00, 0xa7, 0x1e, 0x43, 0xf8, 0x01, 0x54,
|
||||
0x53, 0x42, 0x32, 0x82, 0x42, 0xa1, 0x42, 0xa1, 0x42, 0x61, 0x23, 0x08, 0x46, 0x58, 0x55, 0x41,
|
||||
0x66, 0xfa, 0x33, 0x3c, 0xff, 0xef, 0x42, 0x78, 0x01, 0x84, 0x72, 0xfc, 0x70, 0x0f, 0x14, 0x38,
|
||||
0x02, 0x06, 0xc0, 0x02, 0xd0, 0x40, 0x48, 0x40, 0x10, 0x02, 0xe4, 0x48, 0xc0, 0x41, 0x48, 0x40,
|
||||
0x21, 0xc0, 0x01, 0x8e, 0x70, 0x0f, 0x14, 0x38, 0x02, 0x09, 0xc0, 0x02, 0xe5, 0x48, 0x21, 0xc0,
|
||||
0x02, 0xf4, 0x10, 0x02, 0xe4, 0x48, 0xc0, 0x41, 0x21, 0xc0, 0x02, 0xf0, 0x41, 0xf8, 0x03, 0x40,
|
||||
0x72, 0x50, 0x42, 0x58, 0x51, 0xc9, 0xff, 0xfc, 0x70, 0x1e, 0xc0, 0xfa, 0xff, 0x2e, 0x32, 0x38,
|
||||
0x01, 0x08, 0xe2, 0x49, 0xc0, 0xc1, 0x54, 0x40, 0x32, 0x00, 0xa7, 0x1e, 0x21, 0xc8, 0x03, 0x4e,
|
||||
0x30, 0xc1, 0x31, 0xfc, 0x00, 0x02, 0x03, 0x4c, 0x9e, 0xfc, 0x00, 0x32, 0x20, 0x4f, 0x31, 0x78,
|
||||
0x02, 0x10, 0x00, 0x16, 0xa0, 0x0f, 0x66, 0x00, 0x01, 0xb2, 0xde, 0xfc, 0x00, 0x32, 0x43, 0xf8,
|
||||
0x0a, 0xd8, 0x41, 0xfa, 0xfe, 0x86, 0x70, 0x10, 0xa0, 0x2e, 0x55, 0x4f, 0x2f, 0x0f, 0x48, 0x78,
|
||||
0x09, 0xfa, 0x20, 0x78, 0x08, 0x10, 0x4e, 0x90, 0x30, 0x1f, 0xe6, 0x48, 0x31, 0xc0, 0x01, 0x06,
|
||||
0x08, 0x38, 0x00, 0x06, 0x02, 0x0b, 0x56, 0xf8, 0x08, 0xd3, 0xa8, 0x52, 0x43, 0xfa, 0xfe, 0x9c,
|
||||
0x76, 0x01, 0x61, 0x00, 0x01, 0x98, 0x0c, 0x44, 0x40, 0x00, 0x6e, 0x02, 0x70, 0xff, 0x3f, 0x00,
|
||||
0x66, 0x04, 0x61, 0x00, 0x01, 0xf0, 0xa8, 0x53, 0x55, 0x4f, 0x42, 0xb8, 0x0a, 0xf2, 0xa9, 0x95,
|
||||
0x4a, 0x5f, 0x6b, 0x00, 0x01, 0x56, 0x3e, 0x1f, 0x20, 0x5f, 0xa0, 0x57, 0x21, 0xf8, 0x02, 0xa6,
|
||||
0x01, 0x18, 0x59, 0x4f, 0x2f, 0x3c, 0x44, 0x53, 0x41, 0x54, 0x42, 0x67, 0xa9, 0xa0, 0x2a, 0x1f,
|
||||
0x67, 0x00, 0x01, 0x1e, 0x20, 0x45, 0x21, 0xd0, 0x02, 0xba, 0xa8, 0xfe, 0x70, 0x28, 0x61, 0x00,
|
||||
};
|
||||
const auto data = Storage::Encodings::AppleGCR::Macintosh::data(0, source_data);
|
||||
const auto expected = Storage::Disk::PCMSegment(expected_data);
|
||||
XCTAssertEqual(data.data, expected.data);
|
||||
}
|
||||
|
||||
- (void)testDecoding {
|
||||
const uint8_t format = 0x22;
|
||||
const uint8_t track_id = 23;
|
||||
const bool is_side_two = true;
|
||||
|
||||
// Prepare a test track of 8 sectors.
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment += Storage::Encodings::AppleGCR::six_and_two_sync(24);
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
uint8_t sector_id = uint8_t(c);
|
||||
|
||||
uint8_t sector_plus_tags[524];
|
||||
|
||||
// Provide tags plus a sector body that are just the sector number ad infinitum.
|
||||
for(size_t c = 0; c < sizeof(sector_plus_tags); ++c) {
|
||||
sector_plus_tags[c] = uint8_t(sector_id + (c * 3));
|
||||
}
|
||||
|
||||
// NB: sync lengths below are identical to those for
|
||||
// the Apple II, as I have no idea whatsoever what they
|
||||
// should be.
|
||||
|
||||
segment += Storage::Encodings::AppleGCR::Macintosh::header(
|
||||
format ^ c,
|
||||
track_id - c,
|
||||
sector_id,
|
||||
is_side_two ^ (c & 1)
|
||||
);
|
||||
segment += Storage::Encodings::AppleGCR::six_and_two_sync(7);
|
||||
segment += Storage::Encodings::AppleGCR::Macintosh::data(sector_id, sector_plus_tags);
|
||||
segment += Storage::Encodings::AppleGCR::six_and_two_sync(20);
|
||||
}
|
||||
|
||||
// Parse the prepared track to look for sectors.
|
||||
const auto decoded_sectors = Storage::Encodings::AppleGCR::sectors_from_segment(segment);
|
||||
|
||||
// Assert that the proper number of sectors was found.
|
||||
XCTAssertEqual(decoded_sectors.size(), 8);
|
||||
|
||||
// Assert that the sector descriptions and contents are correct.
|
||||
int sector = 0;
|
||||
for(const auto &pair: decoded_sectors) {
|
||||
XCTAssertFalse(pair.second.has_header_checksum_error);
|
||||
XCTAssertFalse(pair.second.has_data_checksum_error);
|
||||
|
||||
XCTAssertEqual(pair.second.address.is_side_two, is_side_two ^ (sector & 1));
|
||||
XCTAssertEqual(pair.second.address.format, format ^ sector);
|
||||
XCTAssertEqual(pair.second.address.track, track_id - sector);
|
||||
XCTAssertEqual(pair.second.address.sector, sector);
|
||||
|
||||
for(size_t c = 0; c < sizeof(pair.second.data.size()); ++c) {
|
||||
XCTAssertEqual(pair.second.data[c], uint8_t(sector + (c * 3)));
|
||||
}
|
||||
|
||||
++sector;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
46
OSBindings/Mac/Clock SignalTests/MacintoshVideoTests.mm
Normal file
46
OSBindings/Mac/Clock SignalTests/MacintoshVideoTests.mm
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// MacintoshVideoTests.m
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 09/07/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include <memory>
|
||||
#include "../../../Machines/Apple/Macintosh/Video.hpp"
|
||||
|
||||
@interface MacintoshVideoTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation MacintoshVideoTests {
|
||||
Apple::Macintosh::DeferredAudio _dummy_audio;
|
||||
Apple::Macintosh::DriveSpeedAccumulator _dummy_drive_speed_accumulator;
|
||||
std::unique_ptr<Apple::Macintosh::Video> _video;
|
||||
uint16_t _ram[64*1024];
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
_video.reset(new Apple::Macintosh::Video(_ram, _dummy_audio, _dummy_drive_speed_accumulator));
|
||||
}
|
||||
|
||||
- (void)testPrediction {
|
||||
int c = 5;
|
||||
bool vsync = _video->vsync();
|
||||
while(c--) {
|
||||
int remaining_time_in_state = _video->get_next_sequence_point().as_int();
|
||||
NSLog(@"Vsync %@ expected for %@ half-cycles", vsync ? @"on" : @"off", @(remaining_time_in_state));
|
||||
while(remaining_time_in_state--) {
|
||||
XCTAssertEqual(vsync, _video->vsync());
|
||||
_video->run_for(HalfCycles(1));
|
||||
|
||||
if(remaining_time_in_state)
|
||||
XCTAssertEqual(remaining_time_in_state, _video->get_next_sequence_point().as_int());
|
||||
}
|
||||
vsync ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
142
OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp
Normal file
142
OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp
Normal file
@ -0,0 +1,142 @@
|
||||
//
|
||||
// TestRunner68000.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TestRunner68000_h
|
||||
#define TestRunner68000_h
|
||||
|
||||
#include <array>
|
||||
|
||||
#define LOG_TRACE
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
|
||||
using Flag = CPU::MC68000::Flag;
|
||||
|
||||
/*!
|
||||
Provides a 68000 with 64kb of RAM in its low address space;
|
||||
/RESET will put the supervisor stack pointer at 0xFFFF and
|
||||
begin execution at 0x0400.
|
||||
*/
|
||||
class RAM68000: public CPU::MC68000::BusHandler {
|
||||
public:
|
||||
RAM68000() : m68000_(*this) {
|
||||
// Setup the /RESET vector.
|
||||
ram_[0] = 0;
|
||||
ram_[1] = 0x206; // Supervisor stack pointer.
|
||||
ram_[2] = 0;
|
||||
ram_[3] = 0x1000; // Initial PC.
|
||||
|
||||
// Ensure the condition codes start unset.
|
||||
auto state = get_processor_state();
|
||||
state.status &= ~Flag::ConditionCodes;
|
||||
set_processor_state(state);
|
||||
}
|
||||
|
||||
void set_program(const std::vector<uint16_t> &program) {
|
||||
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
|
||||
|
||||
// Add a NOP suffix, to avoid corrupting flags should the attempt to
|
||||
// run for a certain number of instructions overrun.
|
||||
ram_[(0x1000 >> 1) + program.size()] = 0x4e71;
|
||||
}
|
||||
|
||||
void set_initial_stack_pointer(uint32_t sp) {
|
||||
ram_[0] = sp >> 16;
|
||||
ram_[1] = sp & 0xffff;
|
||||
}
|
||||
|
||||
void will_perform(uint32_t address, uint16_t opcode) {
|
||||
--instructions_remaining_;
|
||||
}
|
||||
|
||||
void run_for_instructions(int count) {
|
||||
instructions_remaining_ = count + (has_run_ ? 0 : 1);
|
||||
finish_reset_if_needed();
|
||||
|
||||
while(instructions_remaining_) {
|
||||
run_for(HalfCycles(2));
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
finish_reset_if_needed();
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
void finish_reset_if_needed() {
|
||||
// If the 68000 hasn't run yet, build in the necessary
|
||||
// cycles to finish the reset program, and set the stored state.
|
||||
if(!has_run_) {
|
||||
has_run_ = true;
|
||||
m68000_.run_for(HalfCycles(76));
|
||||
duration_ -= HalfCycles(76);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t *ram_at(uint32_t address) {
|
||||
return &ram_[(address >> 1) % ram_.size()];
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) {
|
||||
const uint32_t word_address = cycle.word_address();
|
||||
if(instructions_remaining_) duration_ += cycle.length;
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||
cycle.value->halves.low = 10;
|
||||
} else {
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
||||
default: break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = ram_[word_address % ram_.size()];
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = ram_[word_address % ram_.size()] >> cycle.byte_shift();
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
ram_[word_address % ram_.size()] = cycle.value->full;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
ram_[word_address % ram_.size()] = uint16_t(
|
||||
(cycle.value->halves.low << cycle.byte_shift()) |
|
||||
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true>::State get_processor_state() {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
void set_processor_state(const CPU::MC68000::Processor<RAM68000, true>::State &state) {
|
||||
m68000_.set_state(state);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true, true> &processor() {
|
||||
return m68000_;
|
||||
}
|
||||
|
||||
int get_cycle_count() {
|
||||
return duration_.as_int() >> 1;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
|
||||
std::array<uint16_t, 256*1024> ram_{};
|
||||
int instructions_remaining_;
|
||||
HalfCycles duration_;
|
||||
bool has_run_ = false;
|
||||
};
|
||||
|
||||
#endif /* TestRunner68000_h */
|
@ -28,6 +28,7 @@ SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/DiskII/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Macintosh/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp')
|
||||
@ -37,6 +38,7 @@ SOURCES += glob.glob('../../Components/1770/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/6560/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/8272/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/8530/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/9918/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/9918/Implementation/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/AudioToggle/*.cpp')
|
||||
@ -53,7 +55,8 @@ SOURCES += glob.glob('../../Inputs/*.cpp')
|
||||
|
||||
SOURCES += glob.glob('../../Machines/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/AmstradCPC/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/AppleII/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/Apple/AppleII/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/Apple/Macintosh/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/Atari2600/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/ColecoVision/*.cpp')
|
||||
SOURCES += glob.glob('../../Machines/Commodore/*.cpp')
|
||||
|
@ -583,6 +583,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Run the main event loop until the OS tells us to quit.
|
||||
const bool uses_mouse = !!machine->mouse_machine();
|
||||
bool should_quit = false;
|
||||
Uint32 fullscreen_mode = 0;
|
||||
while(!should_quit) {
|
||||
@ -621,6 +622,11 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Use ctrl+escape to release the mouse (if captured).
|
||||
if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) {
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
}
|
||||
|
||||
// Capture ctrl+shift+d as a take-a-screenshot command.
|
||||
if(event.key.keysym.sym == SDLK_d && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) {
|
||||
// Grab the screen buffer.
|
||||
@ -722,6 +728,29 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if(uses_mouse && !SDL_GetRelativeMouseMode()) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
break;
|
||||
}
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
const auto mouse_machine = machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
mouse_machine->get_mouse().set_button_pressed(
|
||||
event.button.button % mouse_machine->get_mouse().get_number_of_buttons(),
|
||||
event.type == SDL_MOUSEBUTTONDOWN);
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_MOUSEMOTION: {
|
||||
if(SDL_GetRelativeMouseMode()) {
|
||||
const auto mouse_machine = machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
mouse_machine->get_mouse().move(event.motion.xrel, event.motion.yrel);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,11 @@ void CRT::set_new_data_type(Outputs::Display::InputDataType data_type) {
|
||||
scan_target_->set_modals(scan_target_modals_);
|
||||
}
|
||||
|
||||
void CRT::set_aspect_ratio(float aspect_ratio) {
|
||||
scan_target_modals_.aspect_ratio = aspect_ratio;
|
||||
scan_target_->set_modals(scan_target_modals_);
|
||||
}
|
||||
|
||||
void CRT::set_visible_area(Outputs::Display::Rect visible_area) {
|
||||
scan_target_modals_.visible_area = visible_area;
|
||||
scan_target_->set_modals(scan_target_modals_);
|
||||
|
@ -146,6 +146,10 @@ class CRT {
|
||||
*/
|
||||
void set_new_data_type(Outputs::Display::InputDataType data_type);
|
||||
|
||||
/*! Sets the CRT's intended aspect ratio — 4.0/3.0 by default.
|
||||
*/
|
||||
void set_aspect_ratio(float aspect_ratio);
|
||||
|
||||
/*! Output at the sync level.
|
||||
|
||||
@param number_of_cycles The amount of time to putput sync for.
|
||||
|
@ -23,7 +23,7 @@ void ScanTarget::set_uniforms(ShaderType type, Shader &target) const {
|
||||
case ShaderType::Composition: break;
|
||||
default:
|
||||
target.set_uniform("rowHeight", GLfloat(1.05f / modals_.expected_vertical_lines));
|
||||
target.set_uniform("scale", GLfloat(modals_.output_scale.x), GLfloat(modals_.output_scale.y));
|
||||
target.set_uniform("scale", GLfloat(modals_.output_scale.x), GLfloat(modals_.output_scale.y) * modals_.aspect_ratio * (3.0f / 4.0f));
|
||||
target.set_uniform("phaseOffset", GLfloat(modals_.input_data_tweaks.phase_linked_luminance_offset));
|
||||
|
||||
const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator);
|
||||
|
@ -190,6 +190,9 @@ struct ScanTarget {
|
||||
struct {
|
||||
uint16_t x, y;
|
||||
} output_scale;
|
||||
|
||||
/// Describes the intended display aspect ratio.
|
||||
float aspect_ratio = 4.0f / 3.0f;
|
||||
};
|
||||
|
||||
/// Sets the total format of input data.
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
namespace Outputs {
|
||||
namespace Speaker {
|
||||
@ -61,7 +62,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
std::lock_guard<std::mutex> lock_guard(filter_parameters_mutex_);
|
||||
filter_parameters_.output_cycles_per_second = cycles_per_second;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
output_buffer_.resize(static_cast<std::size_t>(buffer_size));
|
||||
output_buffer_.resize(std::size_t(buffer_size));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -105,7 +106,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
void run_for(const Cycles cycles) {
|
||||
if(!delegate_) return;
|
||||
|
||||
std::size_t cycles_remaining = static_cast<size_t>(cycles.as_int());
|
||||
std::size_t cycles_remaining = size_t(cycles.as_int());
|
||||
if(!cycles_remaining) return;
|
||||
|
||||
FilterParameters filter_parameters;
|
||||
@ -125,7 +126,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
if( filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second &&
|
||||
filter_parameters.high_frequency_cutoff < 0.0) {
|
||||
while(cycles_remaining) {
|
||||
std::size_t cycles_to_read = std::min(output_buffer_.size() - output_buffer_pointer_, cycles_remaining);
|
||||
const auto cycles_to_read = std::min(output_buffer_.size() - output_buffer_pointer_, cycles_remaining);
|
||||
|
||||
sample_source_.get_samples(cycles_to_read, &output_buffer_[output_buffer_pointer_]);
|
||||
output_buffer_pointer_ += cycles_to_read;
|
||||
@ -142,11 +143,11 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the output rate is less than the input rate, or an additional cut-off has been specified, use the filter.
|
||||
// If the output rate is less than the input rate, or an additional cut-off has been specified, use the filter.
|
||||
if( filter_parameters.input_cycles_per_second > filter_parameters.output_cycles_per_second ||
|
||||
(filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second && filter_parameters.high_frequency_cutoff >= 0.0)) {
|
||||
while(cycles_remaining) {
|
||||
std::size_t cycles_to_read = std::min(cycles_remaining, input_buffer_.size() - input_buffer_depth_);
|
||||
const auto cycles_to_read = std::min(cycles_remaining, input_buffer_.size() - input_buffer_depth_);
|
||||
sample_source_.get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]);
|
||||
cycles_remaining -= cycles_to_read;
|
||||
input_buffer_depth_ += cycles_to_read;
|
||||
@ -164,9 +165,9 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
// If the next loop around is going to reuse some of the samples just collected, use a memmove to
|
||||
// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip
|
||||
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||
uint64_t steps = stepper_->step();
|
||||
const auto steps = stepper_->step();
|
||||
if(steps < input_buffer_.size()) {
|
||||
int16_t *input_buffer = input_buffer_.data();
|
||||
auto *const input_buffer = input_buffer_.data();
|
||||
std::memmove( input_buffer,
|
||||
&input_buffer[steps],
|
||||
sizeof(int16_t) * (input_buffer_.size() - steps));
|
||||
@ -212,15 +213,15 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
}
|
||||
|
||||
// Make a guess at a good number of taps.
|
||||
std::size_t number_of_taps = static_cast<std::size_t>(
|
||||
std::size_t number_of_taps = std::size_t(
|
||||
ceilf((filter_parameters.input_cycles_per_second + high_pass_frequency) / high_pass_frequency)
|
||||
);
|
||||
number_of_taps = (number_of_taps * 2) | 1;
|
||||
|
||||
output_buffer_pointer_ = 0;
|
||||
stepper_.reset(new SignalProcessing::Stepper(
|
||||
static_cast<uint64_t>(filter_parameters.input_cycles_per_second),
|
||||
static_cast<uint64_t>(filter_parameters.output_cycles_per_second)));
|
||||
uint64_t(filter_parameters.input_cycles_per_second),
|
||||
uint64_t(filter_parameters.output_cycles_per_second)));
|
||||
|
||||
filter_.reset(new SignalProcessing::FIRFilter(
|
||||
static_cast<unsigned int>(number_of_taps),
|
||||
@ -229,7 +230,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
|
||||
high_pass_frequency,
|
||||
SignalProcessing::FIRFilter::DefaultAttenuation));
|
||||
|
||||
input_buffer_.resize(static_cast<std::size_t>(number_of_taps));
|
||||
input_buffer_.resize(std::size_t(number_of_taps));
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
};
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../RegisterSizes.hpp"
|
||||
|
||||
@ -127,14 +128,14 @@ struct Microcycle {
|
||||
// Various inspectors.
|
||||
|
||||
/*! @returns true if any data select line is active; @c false otherwise. */
|
||||
inline bool data_select_active() const {
|
||||
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.
|
||||
*/
|
||||
inline unsigned int byte_shift() const {
|
||||
forceinline unsigned int byte_shift() const {
|
||||
return (((*address) & 1) << 3) ^ 8;
|
||||
}
|
||||
|
||||
@ -143,7 +144,7 @@ struct Microcycle {
|
||||
|
||||
@returns 0x00ff if this byte access wants the low part of a 16-bit word; 0xff00 if it wants the high part.
|
||||
*/
|
||||
inline uint16_t byte_mask() const {
|
||||
forceinline uint16_t byte_mask() const {
|
||||
return uint16_t(0xff00) >> (((*address) & 1) << 3);
|
||||
}
|
||||
|
||||
@ -153,7 +154,7 @@ struct Microcycle {
|
||||
|
||||
@returns 0xff00 if this byte access wants the low part of a 16-bit word; 0x00ff if it wants the high part.
|
||||
*/
|
||||
inline uint16_t untouched_byte_mask() const {
|
||||
forceinline uint16_t untouched_byte_mask() const {
|
||||
return uint16_t(uint16_t(0xff) << (((*address) & 1) << 3));
|
||||
}
|
||||
|
||||
@ -161,21 +162,21 @@ struct Microcycle {
|
||||
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.
|
||||
*/
|
||||
uint16_t write_byte(uint16_t destination) const {
|
||||
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.
|
||||
*/
|
||||
inline int lower_data_select() const {
|
||||
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.
|
||||
*/
|
||||
inline int upper_data_select() const {
|
||||
forceinline int upper_data_select() const {
|
||||
return (operation & SelectByte) & ~((*address & 1) << 3);
|
||||
}
|
||||
|
||||
@ -186,9 +187,21 @@ struct Microcycle {
|
||||
space, address 1 is the second word (i.e. the third and fourth bytes) in
|
||||
the address space, etc.
|
||||
*/
|
||||
uint32_t word_address() const {
|
||||
forceinline uint32_t word_address() const {
|
||||
return (address ? (*address) & 0x00fffffe : 0) >> 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
@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;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool is_resizeable = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -220,6 +233,19 @@ class BusHandler {
|
||||
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];
|
||||
@ -227,6 +253,14 @@ struct ProcessorState {
|
||||
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;
|
||||
@ -256,29 +290,29 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform = false> cla
|
||||
}
|
||||
|
||||
/// Sets the bus error line — @c true for active, @c false for inactive.
|
||||
void set_bus_error(bool bus_error) {
|
||||
inline void set_bus_error(bool bus_error) {
|
||||
bus_error_ = bus_error;
|
||||
}
|
||||
|
||||
/// Sets the interrupt lines, IPL0, IPL1 and IPL2.
|
||||
void set_interrupt_level(int interrupt_level) {
|
||||
inline void set_interrupt_level(int interrupt_level) {
|
||||
bus_interrupt_level_ = interrupt_level;
|
||||
}
|
||||
|
||||
/// Sets the bus request line.
|
||||
/// This are of functionality is TODO.
|
||||
void set_bus_request(bool bus_request) {
|
||||
inline void set_bus_request(bool bus_request) {
|
||||
bus_request_ = bus_request;
|
||||
}
|
||||
|
||||
/// Sets the bus acknowledge line.
|
||||
/// This are of functionality is TODO.
|
||||
void set_bus_acknowledge(bool bus_acknowledge) {
|
||||
inline void set_bus_acknowledge(bool bus_acknowledge) {
|
||||
bus_acknowledge_ = bus_acknowledge;
|
||||
}
|
||||
|
||||
/// Sets the halt line.
|
||||
void set_halt(bool halt) {
|
||||
inline void set_halt(bool halt) {
|
||||
halt_ = halt;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -92,6 +92,7 @@ class ProcessorStorage {
|
||||
|
||||
MOVEb, MOVEw, MOVEl, MOVEq,
|
||||
MOVEAw, MOVEAl,
|
||||
PEA,
|
||||
|
||||
MOVEtoSR, MOVEfromSR,
|
||||
MOVEtoCCR,
|
||||
@ -103,9 +104,10 @@ class ProcessorStorage {
|
||||
BTSTb, BTSTl,
|
||||
BCLRl, BCLRb,
|
||||
CMPb, CMPw, CMPl,
|
||||
CMPAw,
|
||||
TSTb, TSTw, TSTl,
|
||||
|
||||
JMP,
|
||||
JMP, RTS,
|
||||
BRA, Bcc,
|
||||
DBcc,
|
||||
Scc,
|
||||
@ -192,12 +194,12 @@ class ProcessorStorage {
|
||||
|
||||
} action = Action::None;
|
||||
|
||||
inline bool operator ==(const BusStep &rhs) const {
|
||||
forceinline bool operator ==(const BusStep &rhs) const {
|
||||
if(action != rhs.action) return false;
|
||||
return microcycle == rhs.microcycle;
|
||||
}
|
||||
|
||||
inline bool is_terminal() const {
|
||||
forceinline bool is_terminal() const {
|
||||
return action == Action::ScheduleNextProgram;
|
||||
}
|
||||
};
|
||||
@ -323,7 +325,7 @@ class ProcessorStorage {
|
||||
MicroOp(Action action) : MicroOp(int(action)) {}
|
||||
MicroOp(Action action, BusStep *bus_program) : MicroOp(int(action), bus_program) {}
|
||||
|
||||
inline bool is_terminal() const {
|
||||
forceinline bool is_terminal() const {
|
||||
return bus_program == nullptr;
|
||||
}
|
||||
};
|
||||
@ -414,7 +416,7 @@ class ProcessorStorage {
|
||||
Evaluates the conditional described by @c code and returns @c true or @c false to
|
||||
indicate the result of that evaluation.
|
||||
*/
|
||||
inline bool evaluate_condition(uint8_t code) {
|
||||
forceinline bool evaluate_condition(uint8_t code) {
|
||||
switch(code & 0xf) {
|
||||
default:
|
||||
case 0x00: return true; // true
|
||||
@ -445,7 +447,7 @@ class ProcessorStorage {
|
||||
representing a short-form exception — and mutates the status register as if one
|
||||
were beginning.
|
||||
*/
|
||||
inline void populate_trap_steps(uint32_t vector, uint16_t status) {
|
||||
forceinline void populate_trap_steps(uint32_t vector, uint16_t status) {
|
||||
// Fill in the status word value.
|
||||
destination_bus_data_[0].full = status;
|
||||
|
||||
@ -466,7 +468,7 @@ class ProcessorStorage {
|
||||
trap_steps_->microcycle.length = HalfCycles(8);
|
||||
}
|
||||
|
||||
inline void populate_bus_error_steps(uint32_t vector, uint16_t status, uint16_t bus_status, RegisterPair32 faulting_address) {
|
||||
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_[0].halves.low.full = status;
|
||||
destination_bus_data_[0].halves.high.full = bus_status;
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "../RegisterSizes.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
namespace CPU {
|
||||
namespace Z80 {
|
||||
@ -107,20 +108,20 @@ struct PartialMachineCycle {
|
||||
@returns @c true if the processor believes that the bus handler should actually do something with
|
||||
the content of this PartialMachineCycle; @c false otherwise.
|
||||
*/
|
||||
inline bool expects_action() const {
|
||||
forceinline bool expects_action() const {
|
||||
return operation <= Operation::Interrupt;
|
||||
}
|
||||
/*!
|
||||
@returns @c true if this partial machine cycle completes one of the documented full machine cycles;
|
||||
@c false otherwise.
|
||||
*/
|
||||
inline bool is_terminal() const {
|
||||
forceinline bool is_terminal() const {
|
||||
return operation <= Operation::BusAcknowledge;
|
||||
}
|
||||
/*!
|
||||
@returns @c true if this partial machine cycle is a wait cycle; @c false otherwise.
|
||||
*/
|
||||
inline bool is_wait() const {
|
||||
forceinline bool is_wait() const {
|
||||
return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait;
|
||||
}
|
||||
|
||||
|
7
ROMImages/Macintosh/readme.txt
Normal file
7
ROMImages/Macintosh/readme.txt
Normal file
@ -0,0 +1,7 @@
|
||||
ROMs for the Apple Macintosh go here; these are the property of Apple and so are not included in the distribution.
|
||||
|
||||
Please supply one or more of:
|
||||
|
||||
mac128k.rom
|
||||
mac512k.rom
|
||||
macplus.rom
|
@ -15,7 +15,7 @@ using namespace Storage::Disk;
|
||||
Controller::Controller(Cycles clock_rate) :
|
||||
clock_rate_multiplier_(128000000 / clock_rate.as_int()),
|
||||
clock_rate_(clock_rate.as_int() * clock_rate_multiplier_),
|
||||
empty_drive_(new Drive(static_cast<unsigned int>(clock_rate.as_int()), 1, 1)) {
|
||||
empty_drive_(new Drive(clock_rate.as_int(), 1, 1)) {
|
||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
||||
Time one(1);
|
||||
set_expected_bit_length(one);
|
||||
@ -40,7 +40,7 @@ Drive &Controller::get_drive() {
|
||||
|
||||
// MARK: - Drive::EventDelegate
|
||||
|
||||
void Controller::process_event(const Track::Event &event) {
|
||||
void Controller::process_event(const Drive::Event &event) {
|
||||
switch(event.type) {
|
||||
case Track::Event::FluxTransition: pll_->add_pulse(); break;
|
||||
case Track::Event::IndexHole: process_index_hole(); break;
|
||||
|
@ -120,7 +120,7 @@ class Controller:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
|
||||
// for Drive::EventDelegate
|
||||
void process_event(const Track::Event &event) override;
|
||||
void process_event(const Drive::Event &event) override;
|
||||
void advance(const Cycles cycles) override ;
|
||||
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
|
@ -79,9 +79,9 @@ std::shared_ptr<Track> AppleDSK::get_track_at_position(Track::Address address) {
|
||||
|
||||
// Write the sectors.
|
||||
for(uint8_t c = 0; c < 16; ++c) {
|
||||
segment += Encodings::AppleGCR::header(is_prodos_ ? 0x01 : 0xfe, track, c); // Volume number is 0xfe for DOS 3.3, 0x01 for Pro-DOS.
|
||||
segment += Encodings::AppleGCR::AppleII::header(is_prodos_ ? 0x01 : 0xfe, track, c); // Volume number is 0xfe for DOS 3.3, 0x01 for Pro-DOS.
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(7); // Gap 2: 7 sync words.
|
||||
segment += Encodings::AppleGCR::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]);
|
||||
segment += Encodings::AppleGCR::AppleII::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(20); // Gap 3: 20 sync words.
|
||||
}
|
||||
} else {
|
||||
@ -100,7 +100,7 @@ void AppleDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>>
|
||||
std::map<Track::Address, std::vector<uint8_t>> tracks_by_address;
|
||||
for(const auto &pair: tracks) {
|
||||
// Decode the track.
|
||||
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000)));
|
||||
|
||||
// Rearrange sectors into Apple DOS or Pro-DOS order.
|
||||
|
306
Storage/Disk/DiskImage/Formats/MacintoshIMG.cpp
Normal file
306
Storage/Disk/DiskImage/Formats/MacintoshIMG.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
//
|
||||
// DiskCopy42.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MacintoshIMG.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../../Track/PCMTrack.hpp"
|
||||
#include "../../Track/TrackSerialiser.hpp"
|
||||
#include "../../Encodings/AppleGCR/Encoder.hpp"
|
||||
#include "../../Encodings/AppleGCR/SegmentParser.hpp"
|
||||
|
||||
/*
|
||||
File format specifications as referenced below are largely
|
||||
sourced from the documentation at
|
||||
https://wiki.68kmla.org/DiskCopy_4.2_format_specification
|
||||
*/
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
MacintoshIMG::MacintoshIMG(const std::string &file_name) :
|
||||
file_(file_name) {
|
||||
|
||||
// Test 1: is this a raw secctor dump? If so it'll start with
|
||||
// either the magic word 0x4C4B (big endian) or with 0x00000
|
||||
// and be exactly 819,200 bytes long if double sided, or
|
||||
// 409,600 bytes if single sided.
|
||||
//
|
||||
// Luckily, both 0x00 and 0x4c are invalid string length for the proper
|
||||
// DiskCopy 4.2 format, so there's no ambiguity here.
|
||||
const auto name_length = file_.get8();
|
||||
if(name_length == 0x4c || !name_length) {
|
||||
is_diskCopy_file_ = false;
|
||||
if(file_.stats().st_size != 819200 && file_.stats().st_size != 409600)
|
||||
throw Error::InvalidFormat;
|
||||
|
||||
uint32_t magic_word = file_.get8();
|
||||
if(!((name_length == 0x4c && magic_word == 0x4b) || (name_length == 0x00 && magic_word == 0x00)))
|
||||
throw Error::InvalidFormat;
|
||||
|
||||
file_.seek(0, SEEK_SET);
|
||||
if(file_.stats().st_size == 819200) {
|
||||
encoding_ = Encoding::GCR800;
|
||||
format_ = 0x22;
|
||||
data_ = file_.read(819200);
|
||||
} else {
|
||||
encoding_ = Encoding::GCR400;
|
||||
format_ = 0x02;
|
||||
data_ = file_.read(409600);
|
||||
}
|
||||
} else {
|
||||
// DiskCopy 4.2 it is then:
|
||||
//
|
||||
// File format starts with 64 bytes dedicated to the disk name;
|
||||
// this is a Pascal-style string though there is apparently a
|
||||
// bug in one version of Disk Copy that can cause the length to
|
||||
// be one too high.
|
||||
//
|
||||
// Validate the length, then skip the rest of the string.
|
||||
is_diskCopy_file_ = true;
|
||||
if(name_length > 64)
|
||||
throw Error::InvalidFormat;
|
||||
|
||||
// Get the length of the data and tag blocks.
|
||||
file_.seek(64, SEEK_SET);
|
||||
const auto data_block_length = file_.get32be();
|
||||
const auto tag_block_length = file_.get32be();
|
||||
const auto data_checksum = file_.get32be();
|
||||
const auto tag_checksum = file_.get32be();
|
||||
|
||||
// Don't continue with no data.
|
||||
if(!data_block_length)
|
||||
throw Error::InvalidFormat;
|
||||
|
||||
// Check that this is a comprehensible disk encoding.
|
||||
const auto encoding = file_.get8();
|
||||
switch(encoding) {
|
||||
default: throw Error::InvalidFormat;
|
||||
|
||||
case 0: encoding_ = Encoding::GCR400; break;
|
||||
case 1: encoding_ = Encoding::GCR800; break;
|
||||
case 2: encoding_ = Encoding::MFM720; break;
|
||||
case 3: encoding_ = Encoding::MFM1440; break;
|
||||
}
|
||||
format_ = file_.get8();
|
||||
|
||||
// Check the magic number.
|
||||
const auto magic_number = file_.get16be();
|
||||
if(magic_number != 0x0100)
|
||||
throw Error::InvalidFormat;
|
||||
|
||||
// Read the data and tags, and verify that enough data
|
||||
// was present.
|
||||
data_ = file_.read(data_block_length);
|
||||
tags_ = file_.read(tag_block_length);
|
||||
|
||||
if(data_.size() != data_block_length || tags_.size() != tag_block_length)
|
||||
throw Error::InvalidFormat;
|
||||
|
||||
// Verify the two checksums.
|
||||
const auto computed_data_checksum = checksum(data_);
|
||||
const auto computed_tag_checksum = checksum(tags_, 12);
|
||||
|
||||
/*
|
||||
Yuck! It turns out that at least some disk images have incorrect checksums,
|
||||
and other emulators accept them regardless. So this test is disabled, at least
|
||||
for now. It'd probably be smarter to accept the disk image as provisionally
|
||||
incorrect and somehow communicate the issue to the user? Or, much better,
|
||||
verify the filesystem if the checksums don't match.
|
||||
*/
|
||||
(void)data_checksum;
|
||||
(void)computed_data_checksum;
|
||||
(void)tag_checksum;
|
||||
(void)computed_tag_checksum;
|
||||
|
||||
// if(computed_tag_checksum != tag_checksum || computed_data_checksum != data_checksum)
|
||||
// throw Error::InvalidFormat;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MacintoshIMG::checksum(const std::vector<uint8_t> &data, size_t bytes_to_skip) {
|
||||
uint32_t result = 0;
|
||||
|
||||
// Checksum algorithm is: take each two bytes as a big-endian word; add that to a
|
||||
// 32-bit accumulator and then rotate the accumulator right one position.
|
||||
for(size_t c = bytes_to_skip; c < data.size(); c += 2) {
|
||||
const uint16_t next_word = uint16_t((data[c] << 8) | data[c+1]);
|
||||
result += next_word;
|
||||
result = (result >> 1) | (result << 31);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HeadPosition MacintoshIMG::get_maximum_head_position() {
|
||||
return HeadPosition(80);
|
||||
}
|
||||
|
||||
int MacintoshIMG::get_head_count() {
|
||||
// Bit 5 in the format field indicates whether this disk is double
|
||||
// sided, regardless of whether it is GCR or MFM.
|
||||
return 1 + ((format_ & 0x20) >> 5);
|
||||
}
|
||||
|
||||
bool MacintoshIMG::get_is_read_only() {
|
||||
return file_.get_is_known_read_only();
|
||||
}
|
||||
|
||||
std::shared_ptr<::Storage::Disk::Track> MacintoshIMG::get_track_at_position(::Storage::Disk::Track::Address address) {
|
||||
/*
|
||||
The format_ byte has the following meanings:
|
||||
|
||||
GCR:
|
||||
This byte appears on disk as the GCR format nibble in every sector tag.
|
||||
The low five bits are an interleave factor, either:
|
||||
|
||||
'2' for 0 8 1 9 2 10 3 11 4 12 5 13 6 14 7 15; or
|
||||
'4' for 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15.
|
||||
|
||||
Bit 5 indicates double sided or not.
|
||||
|
||||
MFM:
|
||||
The low five bits provide sector size as a multiple of 256 bytes.
|
||||
Bit 5 indicates double sided or not.
|
||||
*/
|
||||
|
||||
std::lock_guard<decltype(buffer_mutex_)> buffer_lock(buffer_mutex_);
|
||||
if(encoding_ == Encoding::GCR400 || encoding_ == Encoding::GCR800) {
|
||||
// Perform a GCR encoding.
|
||||
const auto included_sectors = Storage::Encodings::AppleGCR::Macintosh::sectors_in_track(address.position.as_int());
|
||||
const size_t start_sector = size_t(included_sectors.start * get_head_count() + included_sectors.length * address.head);
|
||||
|
||||
if(start_sector*512 >= data_.size()) return nullptr;
|
||||
|
||||
uint8_t *const sector = &data_[512 * start_sector];
|
||||
uint8_t *const tags = tags_.size() ? &tags_[12 * start_sector] : nullptr;
|
||||
|
||||
Storage::Disk::PCMSegment segment;
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(24);
|
||||
|
||||
// Determine the sector ordering.
|
||||
uint8_t source_sectors[12] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
int destination = 0;
|
||||
for(int c = 0; c < included_sectors.length; ++c) {
|
||||
// Deal with collisions by finding the next non-colliding spot.
|
||||
while(source_sectors[destination] != 0xff) ++destination;
|
||||
source_sectors[destination] = uint8_t(c);
|
||||
destination = (destination + (format_ & 0x1f)) % included_sectors.length;
|
||||
}
|
||||
|
||||
for(int c = 0; c < included_sectors.length; ++c) {
|
||||
const uint8_t sector_id = source_sectors[c];
|
||||
uint8_t sector_plus_tags[524];
|
||||
|
||||
// Copy in the tags, if provided; otherwise generate them.
|
||||
if(tags) {
|
||||
memcpy(sector_plus_tags, &tags[sector_id * 12], 12);
|
||||
} else {
|
||||
// TODO: fill in tags properly.
|
||||
memset(sector_plus_tags, 0, 12);
|
||||
}
|
||||
|
||||
// Copy in the sector body.
|
||||
memcpy(§or_plus_tags[12], §or[sector_id * 512], 512);
|
||||
|
||||
// NB: sync lengths below are identical to those for
|
||||
// the Apple II, as I have no idea whatsoever what they
|
||||
// should be.
|
||||
|
||||
segment += Encodings::AppleGCR::Macintosh::header(
|
||||
format_,
|
||||
uint8_t(address.position.as_int()),
|
||||
sector_id,
|
||||
!!address.head
|
||||
);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(7);
|
||||
segment += Encodings::AppleGCR::Macintosh::data(sector_id, sector_plus_tags);
|
||||
segment += Encodings::AppleGCR::six_and_two_sync(20);
|
||||
}
|
||||
|
||||
// TODO: it seems some tracks are skewed respective to others; investigate further.
|
||||
|
||||
// segment.rotate_right(3000); // Just a test, yo.
|
||||
return std::make_shared<PCMTrack>(segment);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MacintoshIMG::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
|
||||
std::map<Track::Address, std::vector<uint8_t>> tracks_by_address;
|
||||
for(const auto &pair: tracks) {
|
||||
// Determine a data rate for the track.
|
||||
const auto included_sectors = Storage::Encodings::AppleGCR::Macintosh::sectors_in_track(pair.first.position.as_int());
|
||||
|
||||
// Rule of thumb here: there are about 6250 bits per sector.
|
||||
const int data_rate = included_sectors.length * 6250;
|
||||
|
||||
// Decode the track.
|
||||
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
|
||||
Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, data_rate)));
|
||||
|
||||
// Rearrange sectors into ascending order.
|
||||
std::vector<uint8_t> track_contents(static_cast<size_t>(524 * included_sectors.length));
|
||||
for(const auto §or_pair: sector_map) {
|
||||
const size_t target_address = sector_pair.second.address.sector * 524;
|
||||
if(target_address >= track_contents.size() || sector_pair.second.data.size() != 524) continue;
|
||||
memcpy(&track_contents[target_address], sector_pair.second.data.data(), 524);
|
||||
}
|
||||
|
||||
// Store for later.
|
||||
tracks_by_address[pair.first] = std::move(track_contents);
|
||||
}
|
||||
|
||||
// Grab the buffer mutex and update the in-memory buffer.
|
||||
{
|
||||
std::lock_guard<decltype(buffer_mutex_)> buffer_lock(buffer_mutex_);
|
||||
for(const auto &pair: tracks_by_address) {
|
||||
const auto included_sectors = Storage::Encodings::AppleGCR::Macintosh::sectors_in_track(pair.first.position.as_int());
|
||||
size_t start_sector = size_t(included_sectors.start * get_head_count() + included_sectors.length * pair.first.head);
|
||||
|
||||
for(int c = 0; c < included_sectors.length; ++c) {
|
||||
const auto sector_plus_tags = &pair.second[size_t(c)*524];
|
||||
|
||||
// Copy the 512 bytes that constitute the sector body.
|
||||
memcpy(&data_[start_sector * 512], §or_plus_tags[12], 512);
|
||||
|
||||
// Copy the tags if this file can store them.
|
||||
// TODO: add tags to a DiskCopy-format image that doesn't have them, if they contain novel content?
|
||||
if(tags_.size()) {
|
||||
memcpy(&tags_[start_sector * 12], sector_plus_tags, 12);
|
||||
}
|
||||
|
||||
++start_sector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the file lock and write out the new tracks.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
|
||||
|
||||
if(!is_diskCopy_file_) {
|
||||
// Just dump out the new sectors. Grossly lazy, possibly worth improving.
|
||||
file_.seek(0, SEEK_SET);
|
||||
file_.write(data_);
|
||||
} else {
|
||||
// Write out the sectors, and possibly the tags, and update checksums.
|
||||
file_.seek(0x54, SEEK_SET);
|
||||
file_.write(data_);
|
||||
file_.write(tags_);
|
||||
|
||||
const auto data_checksum = checksum(data_);
|
||||
const auto tag_checksum = checksum(tags_, 12);
|
||||
|
||||
file_.seek(0x48, SEEK_SET);
|
||||
file_.put_be(data_checksum);
|
||||
file_.put_be(tag_checksum);
|
||||
}
|
||||
}
|
||||
}
|
64
Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp
Normal file
64
Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// MacintoshIMG.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MacintoshIMG_hpp
|
||||
#define MacintoshIMG_hpp
|
||||
|
||||
#include "../DiskImage.hpp"
|
||||
#include "../../../FileHolder.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
Provides a @c DiskImage containing either:
|
||||
|
||||
* a disk imaged by Apple's Disk Copy 4.2: sector contents (optionally plus tag data),
|
||||
in either an Apple GCR or standard MFM encoding; or
|
||||
* a raw sector dump of a Macintosh GCR disk.
|
||||
*/
|
||||
class MacintoshIMG: public DiskImage {
|
||||
public:
|
||||
/*!
|
||||
Construct a @c DiskCopy42 containing content from the file with name @c file_name.
|
||||
|
||||
@throws Error::InvalidFormat if this file doesn't appear to be in Disk Copy 4.2 format.
|
||||
*/
|
||||
MacintoshIMG(const std::string &file_name);
|
||||
|
||||
// implemented to satisfy @c Disk
|
||||
HeadPosition get_maximum_head_position() override;
|
||||
int get_head_count() override;
|
||||
bool get_is_read_only() override;
|
||||
|
||||
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override;
|
||||
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) override;
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
|
||||
enum class Encoding {
|
||||
GCR400,
|
||||
GCR800,
|
||||
MFM720,
|
||||
MFM1440
|
||||
} encoding_;
|
||||
uint8_t format_;
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
std::vector<uint8_t> tags_;
|
||||
bool is_diskCopy_file_ = false;
|
||||
std::mutex buffer_mutex_;
|
||||
|
||||
uint32_t checksum(const std::vector<uint8_t> &, size_t bytes_to_skip = 0);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DiskCopy42_hpp */
|
58
Storage/Disk/DiskImage/Formats/PlusTooBIN.cpp
Normal file
58
Storage/Disk/DiskImage/Formats/PlusTooBIN.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// PlusTooBIN.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "PlusTooBIN.hpp"
|
||||
|
||||
#include "../../Track/PCMTrack.hpp"
|
||||
#include "../../Encodings/AppleGCR/Encoder.hpp"
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
namespace {
|
||||
const long sector_size = 1024;
|
||||
}
|
||||
|
||||
PlusTooBIN::PlusTooBIN(const std::string &file_name) :
|
||||
file_(file_name) {
|
||||
// BIN isn't really meant to be an emulator file format, it's primarily
|
||||
// a convenience for the PlusToo Macintosh clone. So validation is
|
||||
// fairly light.
|
||||
if(file_.stats().st_size != 1638400)
|
||||
throw Error::InvalidFormat;
|
||||
}
|
||||
|
||||
HeadPosition PlusTooBIN::get_maximum_head_position() {
|
||||
return HeadPosition(80);
|
||||
}
|
||||
|
||||
int PlusTooBIN::get_head_count() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> PlusTooBIN::get_track_at_position(Track::Address address) {
|
||||
if(address.position >= get_maximum_head_position()) return nullptr;
|
||||
if(address.head >= get_head_count()) return nullptr;
|
||||
|
||||
const auto start_position = Encodings::AppleGCR::Macintosh::sectors_in_track(address.position.as_int());
|
||||
const long file_offset = long(start_position.start * 2 + address.head * start_position.length) * sector_size;
|
||||
file_.seek(file_offset, SEEK_SET);
|
||||
|
||||
const auto track_contents = file_.read(std::size_t(sector_size * start_position.length));
|
||||
|
||||
// Split up the data that comes out per encoded sector, prefixing proper sync bits.
|
||||
Storage::Disk::PCMSegment segment;
|
||||
for(size_t c = 0; c < size_t(start_position.length); ++c) {
|
||||
segment += Storage::Encodings::AppleGCR::six_and_two_sync(5);
|
||||
|
||||
size_t data_start = 0;
|
||||
while(track_contents[c*1024 + data_start] == 0xff) ++data_start;
|
||||
segment += PCMSegment((1024 - data_start) * 8, &track_contents[c*1024 + data_start]);
|
||||
}
|
||||
|
||||
return std::make_shared<PCMTrack>(segment);
|
||||
}
|
40
Storage/Disk/DiskImage/Formats/PlusTooBIN.hpp
Normal file
40
Storage/Disk/DiskImage/Formats/PlusTooBIN.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// PlusTooBIN.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/06/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PlusTooBIN_hpp
|
||||
#define PlusTooBIN_hpp
|
||||
|
||||
#include "../DiskImage.hpp"
|
||||
#include "../../../FileHolder.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
Provides a @c DiskImage capturing the raw bitstream contained in a PlusToo-style BIN file.
|
||||
*/
|
||||
class PlusTooBIN: public DiskImage {
|
||||
public:
|
||||
PlusTooBIN(const std::string &file_name);
|
||||
|
||||
// Implemented to satisfy @c DiskImage.
|
||||
HeadPosition get_maximum_head_position() override;
|
||||
int get_head_count() override;
|
||||
std::shared_ptr<Track> get_track_at_position(Track::Address address) override;
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* PlusTooBIN_hpp */
|
@ -18,11 +18,10 @@
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads):
|
||||
Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads):
|
||||
Storage::TimedEventLoop(input_clock_rate),
|
||||
rotational_multiplier_(60, revolutions_per_minute),
|
||||
rotational_multiplier_(60.0f / float(revolutions_per_minute)),
|
||||
available_heads_(number_of_heads) {
|
||||
rotational_multiplier_.simplify();
|
||||
|
||||
const auto seed = static_cast<std::default_random_engine::result_type>(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine randomiser(seed);
|
||||
@ -36,6 +35,14 @@ Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute, int numb
|
||||
}
|
||||
}
|
||||
|
||||
Drive::Drive(int input_clock_rate, int number_of_heads) : Drive(input_clock_rate, 300, number_of_heads) {}
|
||||
|
||||
void Drive::set_rotation_speed(float revolutions_per_minute) {
|
||||
// TODO: probably I should look into
|
||||
// whether doing all this with quotients is really a good idea.
|
||||
rotational_multiplier_ = 60.0f / revolutions_per_minute;
|
||||
}
|
||||
|
||||
Drive::~Drive() {
|
||||
if(disk_) disk_->flush_tracks();
|
||||
}
|
||||
@ -46,6 +53,7 @@ void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||
has_disk_ = !!disk_;
|
||||
|
||||
invalidate_track();
|
||||
did_set_disk();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
@ -75,6 +83,9 @@ void Drive::step(HeadPosition offset) {
|
||||
if(head_position_ != old_head_position) {
|
||||
track_ = nullptr;
|
||||
}
|
||||
|
||||
// Allow a subclass to react, if desired.
|
||||
did_step(head_position_);
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> Drive::step_to(HeadPosition offset) {
|
||||
@ -97,14 +108,26 @@ void Drive::set_head(int head) {
|
||||
}
|
||||
}
|
||||
|
||||
Storage::Time Drive::get_time_into_track() {
|
||||
// `result` will initially be amount of time since the index hole was seen as a
|
||||
// proportion of a second; convert it into proportion of a rotation, simplify and return.
|
||||
Time result(cycles_since_index_hole_, static_cast<int>(get_input_clock_rate()));
|
||||
result /= rotational_multiplier_;
|
||||
result.simplify();
|
||||
// assert(result <= Time(1));
|
||||
return result;
|
||||
int Drive::get_head_count() {
|
||||
return available_heads_;
|
||||
}
|
||||
|
||||
bool Drive::get_tachometer() {
|
||||
// I have made a guess here that the tachometer is a symmetric square wave;
|
||||
// if that is correct then around 60 beats per rotation appears to be correct
|
||||
// to proceed beyond the speed checks I've so far uncovered.
|
||||
const float ticks_per_rotation = 60.0f; // 56 was too low; 64 too high.
|
||||
return int(get_rotation() * 2.0f * ticks_per_rotation) & 1;
|
||||
}
|
||||
|
||||
float Drive::get_rotation() {
|
||||
return get_time_into_track();
|
||||
}
|
||||
|
||||
float Drive::get_time_into_track() {
|
||||
// i.e. amount of time since the index hole was seen, as a proportion of a second,
|
||||
// converted to a proportion of a rotation.
|
||||
return float(cycles_since_index_hole_) / (float(get_input_clock_rate()) * rotational_multiplier_);
|
||||
}
|
||||
|
||||
bool Drive::get_is_read_only() {
|
||||
@ -144,7 +167,7 @@ void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) {
|
||||
}
|
||||
|
||||
void Drive::advance(const Cycles cycles) {
|
||||
cycles_since_index_hole_ += static_cast<unsigned int>(cycles.as_int());
|
||||
cycles_since_index_hole_ += cycles.as_int();
|
||||
if(event_delegate_) event_delegate_->advance(cycles);
|
||||
}
|
||||
|
||||
@ -184,25 +207,24 @@ void Drive::run_for(const Cycles cycles) {
|
||||
|
||||
// MARK: - Track timed event loop
|
||||
|
||||
void Drive::get_next_event(const Time &duration_already_passed) {
|
||||
void Drive::get_next_event(float duration_already_passed) {
|
||||
// Grab a new track if not already in possession of one. This will recursively call get_next_event,
|
||||
// supplying a proper duration_already_passed.
|
||||
if(!track_) {
|
||||
random_interval_.set_zero();
|
||||
random_interval_ = 0.0f;
|
||||
setup_track();
|
||||
return;
|
||||
}
|
||||
|
||||
// If gain has now been turned up so as to generate noise, generate some noise.
|
||||
if(random_interval_ > Time(0)) {
|
||||
current_event_.type = Track::Event::IndexHole;
|
||||
current_event_.length.length = 2 + (random_source_&1);
|
||||
current_event_.length.clock_rate = 1000000;
|
||||
if(random_interval_ > 0.0f) {
|
||||
current_event_.type = Track::Event::FluxTransition;
|
||||
current_event_.length = float(2 + (random_source_&1)) / 1000000.0f;
|
||||
random_source_ = (random_source_ >> 1) | (random_source_ << 63);
|
||||
|
||||
if(random_interval_ < current_event_.length) {
|
||||
current_event_.length = random_interval_;
|
||||
random_interval_.set_zero();
|
||||
random_interval_ = 0.0f;
|
||||
} else {
|
||||
random_interval_ -= current_event_.length;
|
||||
}
|
||||
@ -211,22 +233,21 @@ void Drive::get_next_event(const Time &duration_already_passed) {
|
||||
}
|
||||
|
||||
if(track_) {
|
||||
current_event_ = track_->get_next_event();
|
||||
const auto track_event = track_->get_next_event();
|
||||
current_event_.type = track_event.type;
|
||||
current_event_.length = track_event.length.get<float>();
|
||||
} else {
|
||||
current_event_.length.length = 1;
|
||||
current_event_.length.clock_rate = 1;
|
||||
current_event_.length = 1.0f;
|
||||
current_event_.type = Track::Event::IndexHole;
|
||||
}
|
||||
|
||||
// divide interval, which is in terms of a single rotation of the disk, by rotation speed to
|
||||
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
||||
assert(current_event_.length <= Time(1) && current_event_.length >= Time(0));
|
||||
assert(current_event_.length > duration_already_passed);
|
||||
Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_;
|
||||
float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f);
|
||||
|
||||
// An interval greater than 15ms => adjust gain up the point where noise starts happening.
|
||||
// Seed that up and leave a 15ms gap until it starts.
|
||||
const Time safe_gain_period(15, 1000000);
|
||||
const float safe_gain_period = 15.0f / 1000000.0f;
|
||||
if(interval >= safe_gain_period) {
|
||||
random_interval_ = interval - safe_gain_period;
|
||||
interval = safe_gain_period;
|
||||
@ -237,7 +258,6 @@ void Drive::get_next_event(const Time &duration_already_passed) {
|
||||
|
||||
void Drive::process_next_event() {
|
||||
if(current_event_.type == Track::Event::IndexHole) {
|
||||
// assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0));
|
||||
if(ready_index_count_ < 2) ready_index_count_++;
|
||||
cycles_since_index_hole_ = 0;
|
||||
}
|
||||
@ -247,7 +267,7 @@ void Drive::process_next_event() {
|
||||
){
|
||||
event_delegate_->process_event(current_event_);
|
||||
}
|
||||
get_next_event(Time(0));
|
||||
get_next_event(0.0f);
|
||||
}
|
||||
|
||||
// MARK: - Track management
|
||||
@ -267,23 +287,20 @@ void Drive::setup_track() {
|
||||
track_.reset(new UnformattedTrack);
|
||||
}
|
||||
|
||||
Time offset;
|
||||
Time track_time_now = get_time_into_track();
|
||||
assert(track_time_now >= Time(0) && current_event_.length <= Time(1));
|
||||
float offset = 0.0f;
|
||||
const auto track_time_now = get_time_into_track();
|
||||
const auto time_found = track_->seek_to(Time(track_time_now)).get<float>();
|
||||
|
||||
Time time_found = track_->seek_to(track_time_now);
|
||||
|
||||
// time_found can be greater than track_time_now if limited precision caused rounding
|
||||
// `time_found` can be greater than `track_time_now` if limited precision caused rounding.
|
||||
if(time_found <= track_time_now) {
|
||||
offset = track_time_now - time_found;
|
||||
} else {
|
||||
offset.set_zero();
|
||||
}
|
||||
|
||||
get_next_event(offset);
|
||||
}
|
||||
|
||||
void Drive::invalidate_track() {
|
||||
random_interval_ = 0.0f;
|
||||
track_ = nullptr;
|
||||
if(patched_track_) {
|
||||
set_track(patched_track_);
|
||||
@ -294,16 +311,25 @@ void Drive::invalidate_track() {
|
||||
// MARK: - Writing
|
||||
|
||||
void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) {
|
||||
// Do nothing if already writing.
|
||||
if(!is_reading_) return;
|
||||
|
||||
// Get a copy of the track if that hasn't happened yet.
|
||||
if(!track_) {
|
||||
setup_track();
|
||||
}
|
||||
|
||||
// Store the relevant parameters, and kick off writing.
|
||||
is_reading_ = false;
|
||||
clamp_writing_to_index_hole_ = clamp_to_index_hole;
|
||||
|
||||
cycles_per_bit_ = Storage::Time(get_input_clock_rate()) * bit_length;
|
||||
cycles_per_bit_.simplify();
|
||||
|
||||
write_segment_.length_of_a_bit = bit_length / rotational_multiplier_;
|
||||
write_segment_.length_of_a_bit = bit_length / Time(rotational_multiplier_);
|
||||
write_segment_.data.clear();
|
||||
|
||||
write_start_time_ = get_time_into_track();
|
||||
write_start_time_ = Time(get_time_into_track());
|
||||
}
|
||||
|
||||
void Drive::write_bit(bool value) {
|
||||
@ -314,7 +340,7 @@ void Drive::write_bit(bool value) {
|
||||
void Drive::end_writing() {
|
||||
// If the user modifies a track, it's scaled up to a "high" resolution and modifications
|
||||
// are plotted on top of that.
|
||||
static const size_t high_resolution_track_rate = 500000;
|
||||
const size_t high_resolution_track_rate = 500000;
|
||||
|
||||
if(!is_reading_) {
|
||||
is_reading_ = true;
|
||||
@ -323,7 +349,7 @@ void Drive::end_writing() {
|
||||
// Avoid creating a new patched track if this one is already patched
|
||||
patched_track_ = std::dynamic_pointer_cast<PCMTrack>(track_);
|
||||
if(!patched_track_ || !patched_track_->is_resampled_clone()) {
|
||||
Track *tr = track_.get();
|
||||
Track *const tr = track_.get();
|
||||
patched_track_.reset(PCMTrack::resampled_clone(tr, high_resolution_track_rate));
|
||||
}
|
||||
}
|
||||
@ -333,6 +359,10 @@ void Drive::end_writing() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Drive::is_writing() {
|
||||
return !is_reading_;
|
||||
}
|
||||
|
||||
void Drive::set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led) {
|
||||
observer_ = observer;
|
||||
announce_motor_led_ = add_motor_led;
|
||||
|
@ -24,7 +24,8 @@ namespace Disk {
|
||||
|
||||
class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
public:
|
||||
Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads);
|
||||
Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads);
|
||||
Drive(int input_clock_rate, int number_of_heads);
|
||||
~Drive();
|
||||
|
||||
/*!
|
||||
@ -53,6 +54,11 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
*/
|
||||
void set_head(int head);
|
||||
|
||||
/*!
|
||||
Gets the head count for this disk.
|
||||
*/
|
||||
int get_head_count();
|
||||
|
||||
/*!
|
||||
@returns @c true if the inserted disk is read-only or no disk is inserted; @c false otherwise.
|
||||
*/
|
||||
@ -94,18 +100,29 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
*/
|
||||
void end_writing();
|
||||
|
||||
/*!
|
||||
@returns @c true if the drive has received a call to begin_writing but not yet a call to
|
||||
end_writing; @c false otherwise.
|
||||
*/
|
||||
bool is_writing();
|
||||
|
||||
/*!
|
||||
Advances the drive by @c number_of_cycles cycles.
|
||||
*/
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
struct Event {
|
||||
Track::Event::Type type;
|
||||
float length = 0.0f;
|
||||
} current_event_;
|
||||
|
||||
/*!
|
||||
Provides a mechanism to receive track events as they occur, including the synthetic
|
||||
event of "you told me to output the following data, and I've done that now".
|
||||
*/
|
||||
struct EventDelegate {
|
||||
/// Informs the delegate that @c event has been reached.
|
||||
virtual void process_event(const Track::Event &event) = 0;
|
||||
virtual void process_event(const Event &event) = 0;
|
||||
|
||||
/*!
|
||||
If the drive is in write mode, announces that all queued bits have now been written.
|
||||
@ -138,6 +155,33 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
*/
|
||||
std::shared_ptr<Track> step_to(HeadPosition offset);
|
||||
|
||||
/*!
|
||||
Alters the rotational velocity of this drive.
|
||||
*/
|
||||
void set_rotation_speed(float revolutions_per_minute);
|
||||
|
||||
/*!
|
||||
@returns the current value of the tachometer pulse offered by some drives.
|
||||
*/
|
||||
bool get_tachometer();
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Announces the result of a step.
|
||||
*/
|
||||
virtual void did_step(HeadPosition to_position) {}
|
||||
|
||||
/*!
|
||||
Announces new media installation.
|
||||
*/
|
||||
virtual void did_set_disk() {}
|
||||
|
||||
/*!
|
||||
@returns the current rotation of the disk, a float in the half-open range
|
||||
0.0 (the index hole) to 1.0 (back to the index hole, a whole rotation later).
|
||||
*/
|
||||
float get_rotation();
|
||||
|
||||
private:
|
||||
// Drives contain an entire disk; from that a certain track
|
||||
// will be currently under the head.
|
||||
@ -147,7 +191,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
|
||||
// Contains the multiplier that converts between track-relative lengths
|
||||
// to real-time lengths. So it's the reciprocal of rotation speed.
|
||||
Time rotational_multiplier_;
|
||||
float rotational_multiplier_;
|
||||
|
||||
// A count of time since the index hole was last seen. Which is used to
|
||||
// determine how far the drive is into a full rotation when switching to
|
||||
@ -183,12 +227,11 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
|
||||
// TimedEventLoop call-ins and state.
|
||||
void process_next_event() override;
|
||||
void get_next_event(const Time &duration_already_passed);
|
||||
void get_next_event(float duration_already_passed);
|
||||
void advance(const Cycles cycles) override;
|
||||
Track::Event current_event_;
|
||||
|
||||
// Helper for track changes.
|
||||
Time get_time_into_track();
|
||||
float get_time_into_track();
|
||||
|
||||
// The target (if any) for track events.
|
||||
EventDelegate *event_delegate_ = nullptr;
|
||||
@ -213,7 +256,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
|
||||
// A rotating random data source.
|
||||
uint64_t random_source_;
|
||||
Time random_interval_;
|
||||
float random_interval_;
|
||||
};
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user