1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-11 04:28:58 +00:00

Merge pull request #662 from TomHarte/AtariST

Adds basic and very buggy Atari ST emulation.
This commit is contained in:
Thomas Harte 2019-11-09 16:18:50 -05:00 committed by GitHub
commit cef07038c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 4056 additions and 370 deletions

View File

@ -15,6 +15,7 @@ enum class Machine {
AmstradCPC,
AppleII,
Atari2600,
AtariST,
ColecoVision,
Electron,
Macintosh,

View File

@ -12,9 +12,10 @@
#include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari;
using namespace Analyser::Static::Atari2600;
using Target = Analyser::Static::Atari2600::Target;
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
@ -48,10 +49,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &targe
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
@ -60,12 +61,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
) {
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
target.paging_model = Target::PagingModel::ActivisionStack;
return;
}
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
target.paging_model = Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@ -85,13 +86,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
tigervision_access_count += masked_address == 0x3f;
}
if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
target.paging_model = Target::PagingModel::Atari16k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@ -106,17 +107,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &targ
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
}
if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
}
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
@ -140,16 +141,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
target.paging_model = Target::PagingModel::Pitfall2;
break;
case 12288:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
target.paging_model = Target::PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
target.paging_model = Target::PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
@ -161,8 +162,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
if( target.paging_model != Target::PagingModel::CBSRamPlus &&
target.paging_model != Target::PagingModel::MNetwork) {
bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
@ -174,19 +175,19 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
}
// check for a Tigervision or Tigervision-esque scheme
if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
}
}
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
// TODO: sanity checking; is this image really for an Atari 2600?
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Atari2600;
target->confidence = 0.5;
target->media.cartridges = media.cartridges;
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
target->paging_model = Target::PagingModel::None;
target->uses_superchip = false;
// try to figure out the paging scheme

View File

@ -15,7 +15,7 @@
namespace Analyser {
namespace Static {
namespace Atari {
namespace Atari2600 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);

View File

@ -6,14 +6,14 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Atari_Target_h
#define Analyser_Static_Atari_Target_h
#ifndef Analyser_Static_Atari2600_Target_h
#define Analyser_Static_Atari2600_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Atari {
namespace Atari2600 {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {

View File

@ -0,0 +1,26 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Target;
auto *target = new Target;
target->machine = Analyser::Machine::AtariST;
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp
#define Analyser_Static_AtariST_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace AtariST {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */

View File

@ -0,0 +1,23 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AtariST_Target_h
#define Analyser_Static_AtariST_Target_h
namespace Analyser {
namespace Static {
namespace AtariST {
struct Target: public ::Analyser::Static::Target {
};
}
}
}
#endif /* Analyser_Static_AtariST_Target_h */

View File

@ -17,7 +17,8 @@
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Atari2600/StaticAnalyser.hpp"
#include "AtariST/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
@ -40,6 +41,7 @@
#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/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
@ -117,6 +119,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
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("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
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
@ -178,7 +181,8 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600);
if(potential_platforms & TargetPlatform::AtariST) Append(AtariST);
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);

View File

@ -10,6 +10,7 @@
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <cstdint>
/*
Informal pattern for all classes that run from a clock cycle:
@ -54,7 +55,9 @@
*/
template <class T> class WrappedInt {
public:
forceinline constexpr WrappedInt(int l) noexcept : length_(l) {}
using IntType = int64_t;
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
forceinline constexpr WrappedInt() noexcept : length_(0) {}
forceinline T &operator =(const T &rhs) {
@ -133,16 +136,20 @@ template <class T> class WrappedInt {
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
forceinline constexpr int as_int() const { return length_; }
/// @returns The underlying int, cast to an integral type of your choosing.
template<typename Type = IntType> forceinline constexpr Type as() { return Type(length_); }
/// @returns The underlying int, in its native form.
forceinline constexpr IntType as_integral() 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.
*/
forceinline T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
template <typename Result = T> forceinline Result divide(const T &divisor) {
Result r;
static_cast<T *>(this)->fill(r, divisor);
return r;
}
/*!
@ -161,13 +168,13 @@ template <class T> class WrappedInt {
// classes that use this template.
protected:
int length_;
IntType length_;
};
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
@ -177,15 +184,20 @@ class Cycles: public WrappedInt<Cycles> {
result.length_ = length_;
length_ = 0;
}
void fill(Cycles &result, const Cycles &divisor) {
result.length_ = length_ / divisor.length_;
length_ %= divisor.length_;
}
};
/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
@ -215,6 +227,16 @@ class HalfCycles: public WrappedInt<HalfCycles> {
result.length_ = length_;
length_ = 0;
}
void fill(Cycles &result, const HalfCycles &divisor) {
result = Cycles(length_ / (divisor.length_ << 1));
length_ %= (divisor.length_ << 1);
}
void fill(HalfCycles &result, const HalfCycles &divisor) {
result.length_ = length_ / divisor.length_;
length_ %= divisor.length_;
}
};
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles

View File

@ -9,9 +9,9 @@
#ifndef ForceInline_hpp
#define ForceInline_hpp
#ifdef DEBUG
#ifndef NDEBUG
#define forceinline
#define forceinline inline
#else

View File

@ -10,6 +10,7 @@
#define JustInTime_h
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ForceInline.hpp"
/*!
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
@ -21,32 +22,44 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
*/
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
public:
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
/// Adds time to the actor.
inline void operator += (const LocalTimeScale &rhs) {
time_since_update_ += rhs;
forceinline void operator += (const LocalTimeScale &rhs) {
if(multiplier != 1) {
time_since_update_ += rhs * multiplier;
} else {
time_since_update_ += rhs;
}
is_flushed_ = false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
inline T *operator->() {
forceinline T *operator->() {
flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
inline T *last_valid() {
forceinline T *last_valid() {
return &object_;
}
/// Flushes all accumulated time.
inline void flush() {
if(!is_flushed_) object_.run_for(time_since_update_.template flush<TargetTimeScale>());
is_flushed_ = true;
forceinline void flush() {
if(!is_flushed_) {
is_flushed_ = true;
if(divider == 1) {
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
} else {
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
}
}
private:

View File

@ -9,6 +9,8 @@
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#define LOG_PREFIX "[WD FDC] "
#include "../../Outputs/Log.hpp"
using namespace WD;
@ -105,7 +107,7 @@ void WD1770::run_for(const Cycles cycles) {
Storage::Disk::Controller::run_for(cycles);
if(delay_time_) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
const auto number_of_cycles = cycles.as_integral();
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(static_cast<int>(Event1770::Timer));
@ -284,7 +286,7 @@ void WD1770::posit_event(int new_event_type) {
goto verify;
}
get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
unsigned int time_to_wait;
Cycles::IntType time_to_wait;
switch(command_ & 3) {
default:
case 0: time_to_wait = 6; break;
@ -767,15 +769,18 @@ void WD1770::posit_event(int new_event_type) {
}
void WD1770::update_status(std::function<void(Status &)> updater) {
const Status old_status = status_;
if(delegate_) {
Status old_status = status_;
updater(status_);
bool did_change =
const bool did_change =
(status_.busy != old_status.busy) ||
(status_.data_request != old_status.data_request);
(status_.data_request != old_status.data_request) ||
(status_.interrupt_request != old_status.interrupt_request);
if(did_change) delegate_->wd1770_did_change_output(this);
}
else updater(status_);
} else updater(status_);
if(status_.busy != old_status.busy) update_clocking_observer();
}
void WD1770::set_head_load_request(bool head_load) {}
@ -785,3 +790,8 @@ void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
}
ClockingHint::Preference WD1770::preferred_clocking() {
if(status_.busy) return ClockingHint::Preference::RealTime;
return Storage::Disk::MFMController::preferred_clocking();
}

View File

@ -73,6 +73,8 @@ class WD1770: public Storage::Disk::MFMController {
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
ClockingHint::Preference preferred_clocking() final;
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
@ -121,7 +123,7 @@ class WD1770: public Storage::Disk::MFMController {
void posit_event(int type);
int interesting_event_mask_;
int resume_point_ = 0;
unsigned int delay_time_ = 0;
Cycles::IntType delay_time_ = 0;
// ID buffer
uint8_t header_[6];

View File

@ -346,7 +346,7 @@ template <typename T> void MOS6522<T>::do_phase1() {
/*! 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();
auto number_of_half_cycles = half_cycles.as_integral();
if(!number_of_half_cycles) return;
if(is_phase2_) {
@ -375,7 +375,7 @@ template <typename T> void MOS6522<T>::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();
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
do_phase1();
do_phase2();

View File

@ -107,7 +107,7 @@ template <class T> class MOS6532 {
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {

View File

@ -170,7 +170,7 @@ template <class BusHandler> class MOS6560 {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += cycles;
int number_of_cycles = cycles.as_int();
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = vertical_counter_;

View File

@ -111,7 +111,7 @@ template <class T> class CRTC6845 {
}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
auto cyles_remaining = cycles.as_integral();
while(cyles_remaining--) {
// check for end of visible characters
if(character_counter_ == registers_[1]) {

189
Components/6850/6850.cpp Normal file
View File

@ -0,0 +1,189 @@
//
// 6850.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "6850.hpp"
using namespace Motorola::ACIA;
const HalfCycles ACIA::SameAsTransmit;
ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
transmit_clock_rate_(transmit_clock_rate),
receive_clock_rate_((receive_clock_rate != SameAsTransmit) ? receive_clock_rate : transmit_clock_rate) {
transmit.set_writer_clock_rate(transmit_clock_rate);
request_to_send.set_writer_clock_rate(transmit_clock_rate);
}
uint8_t ACIA::read(int address) {
if(address&1) {
clear_interrupt_cause(ReceiveNeedsRead);
received_data_ |= NoValueMask;
return uint8_t(received_data_);
} else {
clear_interrupt_cause(StatusNeedsRead);
return
((received_data_ & NoValueMask) ? 0x00 : 0x01) |
((next_transmission_ == NoValueMask) ? 0x02 : 0x00) |
(data_carrier_detect.read() ? 0x04 : 0x00) |
(clear_to_send.read() ? 0x08 : 0x00) |
(interrupt_causes_ ? 0x80 : 0x00)
;
// b0: receive data full.
// b1: transmit data empty.
// b2: DCD.
// b3: CTS.
// b4: framing error (i.e. no first stop bit where expected).
// b5: receiver overran.
// b6: parity error.
// b7: IRQ state.
}
}
void ACIA::write(int address, uint8_t value) {
if(address&1) {
next_transmission_ = value;
consider_transmission();
clear_interrupt_cause(TransmitNeedsWrite);
} else {
if((value&3) == 3) {
transmit.reset_writing();
transmit.write(true);
request_to_send.reset_writing();
} else {
switch(value & 3) {
default:
case 0: divider_ = 1; break;
case 1: divider_ = 16; break;
case 2: divider_ = 64; break;
}
switch((value >> 2) & 7) {
default:
case 0: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break;
case 1: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break;
case 2: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break;
case 3: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break;
case 4: data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None; break;
case 5: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None; break;
case 6: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break;
case 7: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break;
}
switch((value >> 5) & 3) {
case 0: request_to_send.write(false); transmit_interrupt_enabled_ = false; break;
case 1: request_to_send.write(false); transmit_interrupt_enabled_ = true; break;
case 2: request_to_send.write(true); transmit_interrupt_enabled_ = false; break;
case 3:
request_to_send.write(false);
transmit_interrupt_enabled_ = false;
transmit.reset_writing();
transmit.write(false);
break;
}
receive.set_read_delegate(this, Storage::Time(divider_ * 2, int(receive_clock_rate_.as_integral())));
receive_interrupt_enabled_ = value & 0x80;
}
}
update_clocking_observer();
}
void ACIA::consider_transmission() {
if(next_transmission_ != NoValueMask && !transmit.write_data_time_remaining()) {
// Establish start bit and [7 or 8] data bits.
if(data_bits_ == 7) next_transmission_ &= 0x7f;
int transmission = next_transmission_ << 1;
// Add a parity bit, if any.
int mask = 0x2 << data_bits_;
if(parity_ != Parity::None) {
transmission |= parity(uint8_t(next_transmission_)) ? mask : 0;
mask <<= 1;
}
// Add stop bits.
for(int c = 0; c < stop_bits_; ++c) {
transmission |= mask;
mask <<= 1;
}
// Output all that.
const int total_bits = expected_bits();
transmit.write(divider_ * 2, total_bits, transmission);
// Mark the transmit register as empty again.
next_transmission_ = NoValueMask;
}
}
ClockingHint::Preference ACIA::preferred_clocking() {
// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
// is on the receiving end.
if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
// If a bit reception is ongoing that might lead to an interrupt, ask for real-time clocking
// because it's unclear when the interrupt might come.
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
// No clocking required then.
return ClockingHint::Preference::None;
}
bool ACIA::get_interrupt_line() const {
return interrupt_causes_;
}
int ACIA::expected_bits() {
return 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None);
}
uint8_t ACIA::parity(uint8_t value) {
value ^= value >> 4;
value ^= value >> 2;
value ^= value >> 1;
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;
bits_incoming_ = (bits_incoming_ >> 1) | (bit << 10);
// If that's the now-expected number of bits, update.
const int bit_target = expected_bits();
if(bits_received_ >= bit_target) {
bits_received_ = 0;
received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target));
if(receive_interrupt_enabled_) add_interrupt_cause(ReceiveNeedsRead);
update_clocking_observer();
return false;
}
// TODO: overrun, and parity.
// Keep receiving, and consider a potential clocking change.
if(bits_received_ == 1) update_clocking_observer();
return true;
}
void ACIA::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}
void ACIA::add_interrupt_cause(int cause) {
const bool is_changing_state = !interrupt_causes_;
interrupt_causes_ |= cause | StatusNeedsRead;
if(interrupt_delegate_ && is_changing_state)
interrupt_delegate_->acia6850_did_change_interrupt_status(this);
}
void ACIA::clear_interrupt_cause(int cause) {
const bool was_set = interrupt_causes_;
interrupt_causes_ &= ~cause;
if(interrupt_delegate_ && was_set && !interrupt_causes_)
interrupt_delegate_->acia6850_did_change_interrupt_status(this);
}

135
Components/6850/6850.hpp Normal file
View File

@ -0,0 +1,135 @@
//
// 6850.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Motorola_ACIA_6850_hpp
#define Motorola_ACIA_6850_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../Serial/Line.hpp"
namespace Motorola {
namespace ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
/*!
Constructs a new instance of ACIA which will receive a transmission clock at a rate of
@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate.
*/
ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit);
/*!
Reads from the ACIA.
Bit 0 of the address is used as the ACIA's register select line
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
uint8_t read(int address);
/*!
Writes to the ACIA.
Bit 0 of the address is used as the ACIA's register select line
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
void write(int address, uint8_t value);
/*!
Advances @c transmission_cycles in time, which should be
counted relative to the @c transmit_clock_rate.
*/
forceinline void run_for(HalfCycles transmission_cycles) {
if(transmit.transmission_data_time_remaining() > HalfCycles(0)) {
const auto write_data_time_remaining = transmit.write_data_time_remaining();
// There's at most one further byte available to enqueue, so a single 'if'
// rather than a 'while' is correct here. It's the responsibilit of the caller
// to ensure run_for lengths are appropriate for longer sequences.
if(transmission_cycles >= write_data_time_remaining) {
if(next_transmission_ != NoValueMask) {
transmit.advance_writer(write_data_time_remaining);
consider_transmission();
transmit.advance_writer(transmission_cycles - write_data_time_remaining);
} else {
transmit.advance_writer(transmission_cycles);
update_clocking_observer();
if(transmit_interrupt_enabled_) add_interrupt_cause(TransmitNeedsWrite);
}
} else {
transmit.advance_writer(transmission_cycles);
}
}
}
bool get_interrupt_line() const;
// Input lines.
Serial::Line receive;
Serial::Line clear_to_send;
Serial::Line data_carrier_detect;
// Output lines.
Serial::Line transmit;
Serial::Line request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
struct InterruptDelegate {
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
};
void set_interrupt_delegate(InterruptDelegate *delegate);
private:
int divider_ = 1;
enum class Parity {
Even, Odd, None
} parity_ = Parity::None;
int data_bits_ = 7, stop_bits_ = 2;
static const int NoValueMask = 0x100;
int next_transmission_ = NoValueMask;
int received_data_ = NoValueMask;
int bits_received_ = 0;
int bits_incoming_ = 0;
void consider_transmission();
int expected_bits();
uint8_t parity(uint8_t value);
bool receive_interrupt_enabled_ = false;
bool transmit_interrupt_enabled_ = false;
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
enum InterruptCause: int {
TransmitNeedsWrite = 1 << 0,
ReceiveNeedsRead = 1 << 1,
StatusNeedsRead = 1 << 2
};
int interrupt_causes_ = 0;
void add_interrupt_cause(int cause);
void clear_interrupt_cause(int cause);
InterruptDelegate *interrupt_delegate_ = nullptr;
};
}
}
#endif /* Motorola_ACIA_6850_hpp */

View File

@ -0,0 +1,349 @@
//
// MFP68901.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MFP68901.hpp"
#include <cstring>
#define LOG_PREFIX "[MFP] "
//#define NDEBUG
#include "../../Outputs/Log.hpp"
using namespace Motorola::MFP68901;
ClockingHint::Preference MFP68901::preferred_clocking() {
// Rule applied: if any timer is actively running and permitted to produce an
// interrupt, request real-time running.
return
(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
}
uint8_t MFP68901::read(int address) {
address &= 0x1f;
// Interrupt block: various bits of state can be read, all passively.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
switch(address) {
case 0x03: case 0x04: return uint8_t(interrupt_enable_ >> shift);
case 0x05: case 0x06: return uint8_t(interrupt_pending_ >> shift);
case 0x07: case 0x08: return uint8_t(interrupt_in_service_ >> shift);
case 0x09: case 0x0a: return uint8_t(interrupt_mask_ >> shift);
case 0x0b: return interrupt_vector_;
default: break;
}
}
switch(address) {
// GPIP block: input, and configured active edge and direction values.
case 0x00: return (gpip_input_ & ~gpip_direction_) | (gpip_output_ & gpip_direction_);
case 0x01: return gpip_active_edge_;
case 0x02: return gpip_direction_;
/* Interrupt block dealt with above. */
default: break;
// Timer block: read back A, B and C/D control, and read current timer values.
case 0x0c: case 0x0d: return timer_ab_control_[address - 0xc];
case 0x0e: return timer_cd_control_;
case 0x0f: case 0x10:
case 0x11: case 0x12: return get_timer_data(address - 0xf);
// USART block: TODO.
case 0x13: LOG("Read: sync character generator"); break;
case 0x14: LOG("Read: USART control"); break;
case 0x15: LOG("Read: receiver status"); break;
case 0x16: LOG("Read: transmitter status"); break;
case 0x17: LOG("Read: USART data"); break;
}
return 0x00;
}
void MFP68901::write(int address, uint8_t value) {
address &= 0x1f;
// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
const int preserve = 0xff00 >> shift;
const int word_value = value << shift;
switch(address) {
default: break;
case 0x03: case 0x04: // Adjust enabled interrupts; disabled ones also cease to be pending.
interrupt_enable_ = (interrupt_enable_ & preserve) | word_value;
interrupt_pending_ &= interrupt_enable_;
break;
case 0x05: case 0x06: // Resolve pending interrupts.
interrupt_pending_ &= (preserve | word_value);
break;
case 0x07: case 0x08: // Resolve in-service interrupts.
interrupt_in_service_ &= (preserve | word_value);
break;
case 0x09: case 0x0a: // Adjust interrupt mask.
interrupt_mask_ = (interrupt_mask_ & preserve) | word_value;
break;
case 0x0b: // Set the interrupt vector, possibly changing end-of-interrupt mode.
interrupt_vector_ = value;
// If automatic end-of-interrupt mode has now been enabled, clear
// the in-process mask and re-evaluate.
if(interrupt_vector_ & 0x08) return;
interrupt_in_service_ = 0;
break;
}
// Whatever just happened may have affected the state of the interrupt line.
update_interrupts();
update_clocking_observer();
return;
}
const int timer_prescales[] = {
1, 4, 10, 16, 50, 64, 100, 200
};
switch(address) {
// GPIP block: output and configuration of active edge and direction values.
case 0x00:
gpip_output_ = value;
break;
case 0x01:
gpip_active_edge_ = value;
reevaluate_gpip_interrupts();
break;
case 0x02:
gpip_direction_ = value;
reevaluate_gpip_interrupts();
break;
/* Interrupt block dealt with above. */
default: break;
// Timer block.
case 0x0c:
case 0x0d: {
const auto timer = address - 0xc;
const bool reset = value & 0x10;
timer_ab_control_[timer] = value;
switch(value & 0xf) {
case 0x0: set_timer_mode(timer, TimerMode::Stopped, 1, reset); break;
case 0x1: set_timer_mode(timer, TimerMode::Delay, 4, reset); break;
case 0x2: set_timer_mode(timer, TimerMode::Delay, 10, reset); break;
case 0x3: set_timer_mode(timer, TimerMode::Delay, 16, reset); break;
case 0x4: set_timer_mode(timer, TimerMode::Delay, 50, reset); break;
case 0x5: set_timer_mode(timer, TimerMode::Delay, 64, reset); break;
case 0x6: set_timer_mode(timer, TimerMode::Delay, 100, reset); break;
case 0x7: set_timer_mode(timer, TimerMode::Delay, 200, reset); break;
case 0x8: set_timer_mode(timer, TimerMode::EventCount, 1, reset); break;
case 0x9: set_timer_mode(timer, TimerMode::PulseWidth, 4, reset); break;
case 0xa: set_timer_mode(timer, TimerMode::PulseWidth, 10, reset); break;
case 0xb: set_timer_mode(timer, TimerMode::PulseWidth, 16, reset); break;
case 0xc: set_timer_mode(timer, TimerMode::PulseWidth, 50, reset); break;
case 0xd: set_timer_mode(timer, TimerMode::PulseWidth, 64, reset); break;
case 0xe: set_timer_mode(timer, TimerMode::PulseWidth, 100, reset); break;
case 0xf: set_timer_mode(timer, TimerMode::PulseWidth, 200, reset); break;
}
} break;
case 0x0e:
timer_cd_control_ = value;
set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
break;
case 0x0f: case 0x10: case 0x11: case 0x12:
set_timer_data(address - 0xf, value);
break;
// USART block: TODO.
case 0x13: LOG("Write: sync character generator"); break;
case 0x14: LOG("Write: USART control"); break;
case 0x15: LOG("Write: receiver status"); break;
case 0x16: LOG("Write: transmitter status"); break;
case 0x17: LOG("Write: USART data"); break;
}
update_clocking_observer();
}
void MFP68901::run_for(HalfCycles time) {
cycles_left_ += time;
// TODO: this is the stupidest possible implementation. Improve.
int cycles = int(cycles_left_.flush<Cycles>().as_integral());
while(cycles--) {
for(int c = 0; c < 4; ++c) {
if(timers_[c].mode >= TimerMode::Delay) {
--timers_[c].divisor;
if(!timers_[c].divisor) {
timers_[c].divisor = timers_[c].prescale;
decrement_timer(c, 1);
}
}
}
}
// const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
// for(int c = 0; c < 4; ++c) {
// if(timers_[c].mode >= TimerMode::Delay) {
// const int dividend = (cycles + timers_[c].prescale - timers_[c].divisor);
// const int decrements = dividend / timers_[c].prescale;
// timers_[c].divisor = timers_[c].prescale - (dividend % timers_[c].prescale);
// if(decrements) decrement_timer(c, decrements);
// }
// }
}
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles(-1);
}
// MARK: - Timers
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
timers_[timer].mode = mode;
timers_[timer].prescale = prescale;
if(reset_timer) {
timers_[timer].divisor = prescale;
timers_[timer].value = timers_[timer].reload_value;
}
}
void MFP68901::set_timer_data(int timer, uint8_t value) {
if(timers_[timer].mode == TimerMode::Stopped) {
timers_[timer].value = value;
}
timers_[timer].reload_value = value;
}
uint8_t MFP68901::get_timer_data(int timer) {
return timers_[timer].value;
}
void MFP68901::set_timer_event_input(int channel, bool value) {
if(timers_[channel].event_input == value) return;
timers_[channel].event_input = value;
if(timers_[channel].mode == TimerMode::EventCount && !value) { /* TODO: which edge is counted? "as defined by the associated Interrupt Channels edge bit"? */
decrement_timer(channel, 1);
}
}
void MFP68901::decrement_timer(int timer, int amount) {
while(amount--) {
--timers_[timer].value;
if(timers_[timer].value < 1) {
switch(timer) {
case 0: begin_interrupts(Interrupt::TimerA); break;
case 1: begin_interrupts(Interrupt::TimerB); break;
case 2: begin_interrupts(Interrupt::TimerC); break;
case 3: begin_interrupts(Interrupt::TimerD); break;
}
if(timers_[timer].mode == TimerMode::Delay) {
timers_[timer].value += timers_[timer].reload_value; // TODO: properly.
}
}
}
}
// MARK: - GPIP
void MFP68901::set_port_input(uint8_t input) {
gpip_input_ = input;
reevaluate_gpip_interrupts();
}
uint8_t MFP68901::get_port_output() {
return 0xff;
}
void MFP68901::reevaluate_gpip_interrupts() {
const uint8_t gpip_state = (gpip_input_ & ~gpip_direction_) ^ gpip_active_edge_;
// An interrupt is detected on any falling edge.
const uint8_t new_interrupt_mask = (gpip_state ^ gpip_interrupt_state_) & gpip_interrupt_state_;
if(new_interrupt_mask) {
begin_interrupts(
(new_interrupt_mask & 0x0f) |
((new_interrupt_mask & 0x30) << 2) |
((new_interrupt_mask & 0xc0) << 8)
);
}
gpip_interrupt_state_ = gpip_state;
}
// MARK: - Interrupts
void MFP68901::begin_interrupts(int interrupt) {
interrupt_pending_ |= interrupt & interrupt_enable_;
update_interrupts();
}
void MFP68901::end_interrupts(int interrupt) {
interrupt_pending_ &= ~interrupt;
update_interrupts();
}
void MFP68901::update_interrupts() {
const auto old_interrupt_line = interrupt_line_;
const auto firing_interrupts = interrupt_pending_ & interrupt_mask_;
if(!firing_interrupts) {
interrupt_line_ = false;
} else {
if(interrupt_vector_ & 0x8) {
// Software interrupt mode: permit only if neither this interrupt
// nor a higher interrupt is currently in service.
const int highest_bit = msb16(firing_interrupts);
interrupt_line_ = !(interrupt_in_service_ & ~(highest_bit - 1));
} else {
// Auto-interrupt mode; just signal.
interrupt_line_ = true;
}
}
// Update the delegate if necessary.
if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) {
if(interrupt_line_)
LOG("Generating interrupt: " << std::hex << interrupt_pending_ << " / " << std::hex << interrupt_mask_ << " : " << std::hex << interrupt_in_service_);
interrupt_delegate_->mfp68901_did_change_interrupt_status(this);
}
}
bool MFP68901::get_interrupt_line() {
return interrupt_line_;
}
int MFP68901::acknowledge_interrupt() {
if(!(interrupt_pending_ & interrupt_mask_)) {
return NoAcknowledgement;
}
const int mask = msb16(interrupt_pending_ & interrupt_mask_);
// Clear the pending bit regardless.
interrupt_pending_ &= ~mask;
// If this is software interrupt mode, set the in-service bit.
if(interrupt_vector_ & 0x8) {
interrupt_in_service_ |= mask;
}
update_interrupts();
int selected = 0;
while((1 << selected) != mask) ++selected;
LOG("Interrupt acknowledged: " << selected);
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
}
void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}

View File

@ -0,0 +1,157 @@
//
// MFP68901.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MFP68901_hpp
#define MFP68901_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
namespace Motorola {
namespace MFP68901 {
class PortHandler {
public:
// TODO: announce changes in output.
};
class MFP68901: public ClockingHint::Source {
public:
uint8_t read(int address);
void write(int address, uint8_t value);
void run_for(HalfCycles);
HalfCycles get_next_sequence_point();
void set_timer_event_input(int channel, bool value);
void set_port_handler(PortHandler *);
void set_port_input(uint8_t);
uint8_t get_port_output();
bool get_interrupt_line();
static const int NoAcknowledgement = 0x100;
int acknowledge_interrupt();
struct InterruptDelegate {
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
};
void set_interrupt_delegate(InterruptDelegate *delegate);
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
private:
// MARK: - Timers
enum class TimerMode {
Stopped, EventCount, Delay, PulseWidth
};
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
void set_timer_data(int timer, uint8_t);
uint8_t get_timer_data(int timer);
void decrement_timer(int timer, int amount);
struct Timer {
TimerMode mode = TimerMode::Stopped;
uint8_t value = 0;
uint8_t reload_value = 0;
int prescale = 1;
int divisor = 1;
bool event_input = false;
} timers_[4];
uint8_t timer_ab_control_[2] = { 0, 0 };
uint8_t timer_cd_control_ = 0;
HalfCycles cycles_left_;
// MARK: - GPIP
uint8_t gpip_input_ = 0;
uint8_t gpip_output_ = 0;
uint8_t gpip_active_edge_ = 0;
uint8_t gpip_direction_ = 0;
uint8_t gpip_interrupt_state_ = 0;
void reevaluate_gpip_interrupts();
// MARK: - Interrupts
InterruptDelegate *interrupt_delegate_ = nullptr;
// Ad hoc documentation:
//
// An interrupt becomes pending if it is enabled at the time it occurs.
//
// If a pending interrupt is enabled in the interrupt mask, a processor
// interrupt is generated. Otherwise no processor interrupt is generated.
//
// (Disabling a bit in the enabled mask also instantaneously clears anything
// in the pending mask.)
//
// The user can write to the pending interrupt register; a write
// masks whatever is there — so you can disable bits but you cannot set them.
//
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
// its in-service bit. That bit will remain set until the user writes a zero to its position.
// If any bits are set in the in-service register, then they will prevent lower-priority
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
// priority may occur.
//
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
// Acknowledgement of an interrupt will automatically clear the corresponding
// pending bit.
//
int interrupt_enable_ = 0;
int interrupt_pending_ = 0;
int interrupt_mask_ = 0;
int interrupt_in_service_ = 0;
bool interrupt_line_ = false;
uint8_t interrupt_vector_ = 0;
enum Interrupt {
GPIP0 = (1 << 0),
GPIP1 = (1 << 1),
GPIP2 = (1 << 2),
GPIP3 = (1 << 3),
TimerD = (1 << 4),
TimerC = (1 << 5),
GPIP4 = (1 << 6),
GPIP5 = (1 << 7),
TimerB = (1 << 8),
TransmitError = (1 << 9),
TransmitBufferEmpty = (1 << 10),
ReceiveError = (1 << 11),
ReceiveBufferFull = (1 << 12),
TimerA = (1 << 13),
GPIP6 = (1 << 14),
GPIP7 = (1 << 15),
};
void begin_interrupts(int interrupt);
void end_interrupts(int interrupt);
void update_interrupts();
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
inline static int msb16(int v) {
// Saturate all bits below the MSB.
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
// Throw away lesser bits.
return (v+1) >> 1;
}
};
}
}
#endif /* MFP68901_hpp */

View File

@ -95,11 +95,11 @@ void i8272::run_for(Cycles cycles) {
// check for an expired timer
if(delay_time_ > 0) {
if(cycles.as_int() >= delay_time_) {
if(cycles.as_integral() >= delay_time_) {
delay_time_ = 0;
posit_event(static_cast<int>(Event8272::Timer));
} else {
delay_time_ -= cycles.as_int();
delay_time_ -= cycles.as_integral();
}
}
@ -108,8 +108,8 @@ void i8272::run_for(Cycles cycles) {
int drives_left = drives_seeking_;
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::Seeking) {
drives_[c].step_rate_counter += cycles.as_int();
int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
drives_[c].step_rate_counter += cycles.as_integral();
auto steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
while(steps--) {
// Perform a step.
@ -141,12 +141,12 @@ void i8272::run_for(Cycles cycles) {
int head = c&1;
if(drives_[drive].head_unload_delay[head] > 0) {
if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
if(cycles.as_integral() >= drives_[drive].head_unload_delay[head]) {
drives_[drive].head_unload_delay[head] = 0;
drives_[drive].head_is_loaded[head] = false;
head_timers_running_--;
} else {
drives_[drive].head_unload_delay[head] -= cycles.as_int();
drives_[drive].head_unload_delay[head] -= cycles.as_integral();
}
timers_left--;
if(!timers_left) break;

View File

@ -73,7 +73,7 @@ class i8272 : public Storage::Disk::MFMController {
bool is_access_command_ = false;
// The counter used for ::Timer events.
int delay_time_ = 0;
Cycles::IntType delay_time_ = 0;
// The connected drives.
struct Drive {
@ -89,12 +89,12 @@ class i8272 : public Storage::Disk::MFMController {
bool seek_failed = false;
// Seeking: transient state.
int step_rate_counter = 0;
Cycles::IntType step_rate_counter = 0;
int steps_taken = 0;
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
// Head state.
int head_unload_delay[2] = {0, 0};
Cycles::IntType head_unload_delay[2] = {0, 0};
bool head_is_loaded[2] = {false, false};
} drives_[4];

View File

@ -166,7 +166,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
// for this part. So multiply by three quarters.
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
int int_cycles = int(cycles.as_integral() * 3) + cycles_error_;
cycles_error_ = int_cycles & 3;
int_cycles >>= 2;
if(!int_cycles) return;

View File

@ -35,9 +35,10 @@ class PortHandler {
}
/*!
Requests the current input on an AY port.
Sets the current output on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
@param value the value now being output.
*/
virtual void set_port_output(bool port_b, uint8_t value) {}
};

View File

@ -78,7 +78,7 @@ void DiskII::select_drive(int drive) {
void DiskII::run_for(const Cycles cycles) {
if(preferred_clocking() == ClockingHint::Preference::None) return;
int integer_cycles = cycles.as_int();
auto integer_cycles = cycles.as_integral();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
if(flux_duration_) {
@ -124,7 +124,7 @@ void DiskII::run_for(const Cycles cycles) {
// motor switch being flipped and the drive motor actually switching off.
// This models that, accepting overrun as a risk.
if(motor_off_time_ >= 0) {
motor_off_time_ -= cycles.as_int();
motor_off_time_ -= cycles.as_integral();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
@ -266,7 +266,7 @@ int DiskII::read_address(int address) {
break;
case 0xf:
if(!(inputs_ & input_mode))
drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
inputs_ |= input_mode;
break;
}

View File

@ -101,7 +101,7 @@ class DiskII final:
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;
const Cycles::IntType clock_rate_ = 0;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
@ -109,7 +109,7 @@ class DiskII final:
int stepper_mask_ = 0;
int stepper_position_ = 0;
int motor_off_time_ = -1;
Cycles::IntType motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;

View File

@ -233,7 +233,7 @@ void IWM::run_for(const Cycles cycles) {
}
// Activity otherwise depends on mode and motor state.
int integer_cycles = cycles.as_int();
auto integer_cycles = cycles.as_integral();
switch(shift_mode_) {
case ShiftMode::Reading: {
// Per the IWM patent, column 7, around line 35 onwards: "The expected time
@ -241,7 +241,7 @@ void IWM::run_for(const Cycles cycles) {
// expected time since the data is not precisely spaced when read due to
// variations in drive speed and other external factors". The error_margin
// here implements the 'after' part of that contract.
const auto error_margin = Cycles(bit_length_.as_int() >> 1);
const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
if(drive_is_rotating_[active_drive_]) {
while(integer_cycles--) {
@ -254,7 +254,7 @@ void IWM::run_for(const Cycles cycles) {
} else {
while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) {
const auto run_length = bit_length_ + error_margin - cycles_since_shift_;
integer_cycles -= run_length.as_int();
integer_cycles -= run_length.as_integral();
cycles_since_shift_ += run_length;
propose_shift(0);
}
@ -272,7 +272,7 @@ void IWM::run_for(const Cycles cycles) {
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
shift_register_ <<= 1;
integer_cycles -= cycles_until_write.as_int();
integer_cycles -= cycles_until_write.as_integral();
cycles_since_shift_ = Cycles(0);
--output_bits_remaining_;
@ -333,7 +333,7 @@ void IWM::select_shift_mode() {
// 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);
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
@ -369,7 +369,7 @@ void IWM::propose_shift(uint8_t bit) {
// shift in a 1 and start a new window wherever the first found 1 was.
//
// If no 1s are found, shift in a 0 and don't alter expectations as to window placement.
const auto error_margin = Cycles(bit_length_.as_int() >> 1);
const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
if(bit && cycles_since_shift_ < error_margin) return;
shift_register_ = uint8_t((shift_register_ << 1) | bit);

140
Components/Serial/Line.cpp Normal file
View File

@ -0,0 +1,140 @@
//
// SerialPort.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Line.hpp"
using namespace Serial;
void Line::set_writer_clock_rate(HalfCycles clock_rate) {
clock_rate_ = clock_rate;
}
void Line::advance_writer(HalfCycles cycles) {
if(cycles == HalfCycles(0)) return;
const auto integral_cycles = cycles.as_integral();
remaining_delays_ = std::max(remaining_delays_ - integral_cycles, Cycles::IntType(0));
if(events_.empty()) {
write_cycles_since_delegate_call_ += integral_cycles;
if(transmission_extra_) {
transmission_extra_ -= integral_cycles;
if(transmission_extra_ <= 0) {
transmission_extra_ = 0;
update_delegate(level_);
}
}
} else {
while(!events_.empty()) {
if(events_.front().delay <= integral_cycles) {
cycles -= events_.front().delay;
write_cycles_since_delegate_call_ += events_.front().delay;
const auto old_level = level_;
auto iterator = events_.begin() + 1;
while(iterator != events_.end() && iterator->type != Event::Delay) {
level_ = iterator->type == Event::SetHigh;
++iterator;
}
events_.erase(events_.begin(), iterator);
if(old_level != level_) {
update_delegate(old_level);
}
// Book enough extra time for the read delegate to be posted
// the final bit if one is attached.
if(events_.empty()) {
transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
}
} else {
events_.front().delay -= integral_cycles;
write_cycles_since_delegate_call_ += integral_cycles;
break;
}
}
}
}
void Line::write(bool level) {
if(!events_.empty()) {
events_.emplace_back();
events_.back().type = level ? Event::SetHigh : Event::SetLow;
} else {
level_ = level;
transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
}
}
void Line::write(HalfCycles cycles, int count, int levels) {
remaining_delays_ += count * cycles.as_integral();
auto event = events_.size();
events_.resize(events_.size() + size_t(count)*2);
while(count--) {
events_[event].type = Event::Delay;
events_[event].delay = int(cycles.as_integral());
events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
levels >>= 1;
event += 2;
}
}
void Line::reset_writing() {
remaining_delays_ = 0;
events_.clear();
}
bool Line::read() {
return level_;
}
void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
read_delegate_ = delegate;
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
}
void Line::update_delegate(bool level) {
// Exit early if there's no delegate, or if the delegate is waiting for
// zero and this isn't zero.
if(!read_delegate_) return;
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
}
time_left_in_bit_ -= time_left;
}
Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
if(!read_delegate_) return 0;
return 1 + (read_delegate_bit_length_ * static_cast<unsigned int>(clock_rate_.as_integral())).get<int>();
}

112
Components/Serial/Line.hpp Normal file
View File

@ -0,0 +1,112 @@
//
// SerialPort.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef SerialPort_hpp
#define SerialPort_hpp
#include <vector>
#include "../../Storage/Storage.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
namespace Serial {
/*!
@c Line connects a single reader and a single writer, allowing timestamped events to be
published and consumed, potentially with a clock conversion in between. It allows line
levels to be written and read in larger collections.
It is assumed that the owner of the reader and writer will ensure that the reader will never
get ahead of the writer. If the writer posts events behind the reader they will simply be
given instanteous effect.
*/
class Line {
public:
void set_writer_clock_rate(HalfCycles clock_rate);
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Sets the line to @c level.
void write(bool level);
/// Enqueues @c count level changes, the first occurring immediately
/// after the final event currently posted and each subsequent event
/// occurring @c cycles after the previous. An additional gap of @c cycles
/// is scheduled after the final output. The levels to output are
/// taken from @c levels which is read from lsb to msb. @c cycles is
/// relative to the writer's clock rate.
void write(HalfCycles cycles, int count, int levels);
/// @returns the number of cycles until currently enqueued write data is exhausted.
forceinline HalfCycles write_data_time_remaining() const {
return HalfCycles(remaining_delays_);
}
/// @returns the number of cycles left until it is guaranteed that a passive reader
/// has received all currently-enqueued bits.
forceinline HalfCycles transmission_data_time_remaining() const {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
/// @returns The instantaneous level of this line.
bool read();
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
};
/*!
Sets a read delegate, which will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
*/
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
private:
struct Event {
enum Type {
Delay, SetHigh, SetLow
} type;
int delay;
};
std::vector<Event> events_;
HalfCycles::IntType remaining_delays_ = 0;
HalfCycles::IntType transmission_extra_ = 0;
bool level_ = true;
HalfCycles clock_rate_ = 0;
ReadDelegate *read_delegate_ = nullptr;
Storage::Time read_delegate_bit_length_, time_left_in_bit_;
int write_cycles_since_delegate_call_ = 0;
enum class ReadDelegatePhase {
WaitingForZero,
Serialising
} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
void update_delegate(bool level);
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
};
/*!
Defines an RS-232-esque srial port.
*/
class Port {
public:
};
}
#endif /* SerialPort_hpp */

View File

@ -872,7 +872,7 @@ template <bool has_fdc> class ConcreteMachine:
// TODO (in the player, not here): adapt it to accept an input clock rate and
// run_for as HalfCycles
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int());
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral());
// Pump the AY
ay_.run_for(cycle.length);
@ -1157,7 +1157,7 @@ template <bool has_fdc> class ConcreteMachine:
void flush_fdc() {
// Clock the FDC, if connected, using a lazy scale by two
if(has_fdc && !fdc_is_sleeping_) {
fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
fdc_.run_for(Cycles(time_since_fdc_update_.as_integral()));
}
time_since_fdc_update_ = HalfCycles(0);
}

View File

@ -52,7 +52,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
void DiskIICard::run_for(Cycles cycles, int stretches) {
if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
diskii_.run_for(Cycles(cycles.as_int() * 2));
diskii_.run_for(Cycles(cycles.as_integral() * 2));
}
void DiskIICard::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {

View File

@ -284,7 +284,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html
// Determine column at offset.
int mapped_column = column_ + offset.as_int();
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
@ -315,7 +315,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
bool get_is_vertical_blank(Cycles offset) {
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
int mapped_column = column_ + offset.as_int();
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
@ -343,7 +343,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
static const int first_sync_column = 49; // Also a guess.
static const int sync_length = 4; // One of the two likely candidates.
int int_cycles = cycles.as_int();
int int_cycles = int(cycles.as_integral());
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;

View File

@ -576,7 +576,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
forceinline void advance_time(HalfCycles duration) {
time_since_video_update_ += duration;
iwm_ += duration;
ram_subcycle_ = (ram_subcycle_ + duration.as_int()) & 15;
ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15;
// 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
@ -633,7 +633,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// Consider updating the real-time clock.
real_time_clock_ += duration;
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral();
while(ticks--) {
clock_.update();
// TODO: leave a delay between toggling the input rather than using this coupled hack.
@ -656,9 +656,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
return mouse_;
}
using IWMActor = JustInTimeActor<IWM, 1, 1, HalfCycles, Cycles>;
class VIAPortHandler: public MOS::MOS6522::PortHandler {
public:
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, JustInTimeActor<IWM, HalfCycles, Cycles> &iwm, Inputs::QuadratureMouse &mouse) :
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
using Port = MOS::MOS6522::Port;
@ -748,7 +750,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
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);
audio_.time_since_update += HalfCycles(duration.as_integral() * 5);
}
void flush() {
@ -764,14 +766,14 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
RealTimeClock &clock_;
Keyboard &keyboard_;
DeferredAudio &audio_;
JustInTimeActor<IWM, HalfCycles, Cycles> &iwm_;
IWMActor &iwm_;
Inputs::QuadratureMouse &mouse_;
};
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
DriveSpeedAccumulator drive_speed_accumulator_;
JustInTimeActor<IWM, HalfCycles, Cycles> iwm_;
IWMActor iwm_;
DeferredAudio audio_;
Video video_;

View File

@ -47,7 +47,7 @@ void Video::run_for(HalfCycles duration) {
// 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 int line = int((frame_position_ / line_length).as_integral());
const auto cycles_left_in_line = std::min(line_length - pixel_start, duration);
@ -62,8 +62,8 @@ void Video::run_for(HalfCycles duration) {
//
// 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;
const int first_word = int(pixel_start.as_integral()) >> 4;
const int final_word = int((pixel_start + cycles_left_in_line).as_integral()) >> 4;
if(first_word != final_word) {
if(line < 342) {
@ -153,12 +153,12 @@ void Video::run_for(HalfCycles duration) {
}
bool Video::vsync() {
const int line = (frame_position_ / line_length).as_int();
const auto line = (frame_position_ / line_length).as_integral();
return line >= 353 && line < 356;
}
HalfCycles Video::get_next_sequence_point() {
const int line = (frame_position_ / line_length).as_int();
const auto line = (frame_position_ / line_length).as_integral();
if(line >= 353 && line < 356) {
// Currently in vsync, so get time until start of line 357,
// when vsync will end.

View File

@ -69,8 +69,8 @@ class Video {
*/
bool is_outputting(HalfCycles offset = HalfCycles(0)) {
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();
const int column = int((offset_position % line_length).as_integral()) >> 4;
const int line = int((offset_position / line_length).as_integral());
return line < 342 && column < 32;
}

View File

@ -11,10 +11,10 @@
#include <algorithm>
#include <cstdio>
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../../CRTMachine.hpp"
#include "../../JoystickMachine.hpp"
#include "../../Analyser/Static/Atari/Target.hpp"
#include "../../../Analyser/Static/Atari2600/Target.hpp"
#include "Cartridges/Atari8k.hpp"
#include "Cartridges/Atari16k.hpp"
@ -72,18 +72,20 @@ class Joystick: public Inputs::ConcreteJoystick {
std::size_t shift_, fire_tia_input_;
};
using Target = Analyser::Static::Atari2600::Target;
class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public JoystickMachine::Machine,
public Outputs::CRT::Delegate {
public:
ConcreteMachine(const Analyser::Static::Atari::Target &target) {
ConcreteMachine(const Target &target) {
set_clock_rate(NTSC_clock_rate);
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
using PagingModel = Analyser::Static::Atari::Target::PagingModel;
using PagingModel = Target::PagingModel;
switch(target.paging_model) {
case PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
@ -232,7 +234,6 @@ class ConcreteMachine:
using namespace Atari2600;
Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Atari::Target;
const Target *const atari_target = dynamic_cast<const Target *>(target);
return new Atari2600::ConcreteMachine(*atari_target);
}

View File

@ -9,9 +9,9 @@
#ifndef Atari2600_cpp
#define Atari2600_cpp
#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 "Atari2600Inputs.h"

View File

@ -14,9 +14,9 @@
#include "TIA.hpp"
#include "TIASound.hpp"
#include "../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
namespace Atari2600 {

View File

@ -9,7 +9,7 @@
#ifndef Atari2600_Cartridge_hpp
#define Atari2600_Cartridge_hpp
#include "../../../Processors/6502/6502.hpp"
#include "../../../../Processors/6502/6502.hpp"
#include "../Bus.hpp"
namespace Atari2600 {
@ -51,7 +51,7 @@ template<class T> class Cartridge:
Adjusts @c confidence_counter according to the results of the most recent run_for.
*/
void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) {
if(cycle_count_.as_int() < 200) return;
if(cycle_count_.as_integral() < 200) return;
if(horizontal_counter_resets_ > 10)
confidence_counter.add_miss();
}

View File

@ -101,7 +101,7 @@ class Pitfall2: public BusExtender {
inline uint8_t update_audio() {
const unsigned int clock_divisor = 57;
int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int();
int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral());
int table_position = 0;
for(int c = 0; c < 3; c++) {

View File

@ -11,7 +11,7 @@
#include <cstdint>
#include "../../Components/6532/6532.hpp"
#include "../../../Components/6532/6532.hpp"
namespace Atari2600 {

View File

@ -143,7 +143,7 @@ void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
}
void TIA::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
int number_of_cycles = int(cycles.as_integral());
// if part way through a line, definitely perform a partial, at most up to the end of the line
if(horizontal_counter_) {
@ -176,7 +176,7 @@ void TIA::reset_horizontal_counter() {
}
int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line;
return (cycles_per_line - (horizontal_counter_ + from_offset.as_integral()) % cycles_per_line) % cycles_per_line;
}
void TIA::set_background_colour(uint8_t colour) {

View File

@ -14,8 +14,8 @@
#include <cstdint>
#include <functional>
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace Atari2600 {

View File

@ -9,8 +9,8 @@
#ifndef Atari2600_TIASound_hpp
#define Atari2600_TIASound_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../../Concurrency/AsyncTaskQueue.hpp"
namespace Atari2600 {

View File

@ -0,0 +1,576 @@
//
// AtariST.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "AtariST.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../MouseMachine.hpp"
#include "../../MediaTarget.hpp"
#include "../../../Activity/Source.hpp"
//#define LOG_TRACE
#include "../../../Processors/68000/68000.hpp"
#include "../../../Components/AY38910/AY38910.hpp"
#include "../../../Components/68901/MFP68901.hpp"
#include "../../../Components/6850/6850.hpp"
#include "DMAController.hpp"
#include "IntelligentKeyboard.hpp"
#include "Video.hpp"
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Outputs/Log.hpp"
#include "../../Utility/MemoryPacker.hpp"
#include "../../Utility/MemoryFuzzer.hpp"
namespace Atari {
namespace ST {
const int CLOCK_RATE = 8021247;
using Target = Analyser::Static::Target;
class ConcreteMachine:
public Atari::ST::Machine,
public CPU::MC68000::BusHandler,
public CRTMachine::Machine,
public ClockingHint::Observer,
public Motorola::ACIA::ACIA::InterruptDelegate,
public Motorola::MFP68901::MFP68901::InterruptDelegate,
public DMAController::Delegate,
public MouseMachine::Machine,
public KeyboardMachine::MappedMachine,
public Activity::Source,
public MediaTarget::Machine,
public GI::AY38910::PortHandler {
public:
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
keyboard_acia_(Cycles(500000)),
midi_acia_(Cycles(500000)),
ay_(audio_queue_),
speaker_(ay_),
ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) {
set_clock_rate(CLOCK_RATE);
speaker_.set_input_rate(CLOCK_RATE / 4);
ram_.resize(512 * 512); // i.e. 512kb
video_->set_ram(ram_.data(), ram_.size());
Memory::Fuzz(ram_);
std::vector<ROMMachine::ROM> rom_descriptions = {
{"AtariST", "the TOS ROM", "tos100.img", 192*1024, 0x1a586c64}
};
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0]) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(*roms[0], rom_);
// Set up basic memory map.
memory_map_[0] = BusDevice::MostlyRAM;
int c = 1;
for(; c < 0x08; ++c) memory_map_[c] = BusDevice::RAM;
for(; c < 0xff; ++c) memory_map_[c] = BusDevice::Unassigned;
const bool is_early_tos = true;
if(is_early_tos) {
for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM;
} else {
for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM;
}
memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge;
memory_map_[0xff] = BusDevice::IO;
midi_acia_->set_interrupt_delegate(this);
keyboard_acia_->set_interrupt_delegate(this);
midi_acia_->set_clocking_hint_observer(this);
keyboard_acia_->set_clocking_hint_observer(this);
ikbd_.set_clocking_hint_observer(this);
mfp_->set_clocking_hint_observer(this);
dma_->set_clocking_hint_observer(this);
mfp_->set_interrupt_delegate(this);
dma_->set_delegate(this);
ay_.set_port_handler(this);
set_gpip_input();
// Insert any supplied media.
insert_media(target.media);
}
~ConcreteMachine() {
audio_queue_.flush();
}
// MARK: CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_->set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
void run_for(const Cycles cycles) final {
// Give the keyboard an opportunity to consume any events.
if(!keyboard_needs_clock_) {
ikbd_.run_for(HalfCycles(0));
}
mc68000_.run_for(cycles);
}
// MARK: MC68000::BusHandler
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) {
// Just in case the last cycle was an interrupt acknowledge or bus error. TODO: find a better solution?
mc68000_.set_is_peripheral_address(false);
mc68000_.set_bus_error(false);
// Advance time.
advance_time(cycle.length);
// A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
// An interrupt acknowledge, perhaps?
if(cycle.operation & Microcycle::InterruptAcknowledge) {
// Current implementation: everything other than 6 (i.e. the MFP is autovectored.
if((cycle.word_address()&7) != 6) {
mc68000_.set_is_peripheral_address(true);
return HalfCycles(0);
} else {
if(cycle.operation & Microcycle::SelectByte) {
const int interrupt = mfp_->acknowledge_interrupt();
if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) {
cycle.value->halves.low = uint8_t(interrupt);
} else {
// TODO: this should take a while. Find out how long.
mc68000_.set_bus_error(true);
}
}
return HalfCycles(0);
}
}
// If this is a new strobing of the address signal, test for bus error and pre-DTack delay.
HalfCycles delay(0);
if(cycle.operation & Microcycle::NewAddress) {
// DTack will be implicit; work out how long until that should be,
// and apply bus error constraints.
const int i_phase = bus_phase_.as<int>() & 7;
if(i_phase < 4) {
delay = HalfCycles(4 - i_phase);
advance_time(delay);
}
// TODO: presumably test is if(after declared memory size and (not supervisor or before hardware space)) bus_error?
}
auto address = cycle.word_address();
uint16_t *memory = nullptr;
switch(memory_map_[address >> 15]) {
default:
case BusDevice::MostlyRAM:
if(address < 4) {
memory = rom_.data();
break;
}
case BusDevice::RAM:
memory = ram_.data();
address &= ram_.size() - 1;
// TODO: align with the next access window.
break;
case BusDevice::ROM:
memory = rom_.data();
address %= rom_.size();
break;
case BusDevice::Unassigned:
// TODO: figure out the rules about bus errors.
case BusDevice::Cartridge:
/*
TOS 1.0 appears to attempt to read from the catridge before it has setup
the bus error vector. Therefore I assume no bus error flows.
*/
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
default: break;
case Microcycle::SelectWord | Microcycle::Read:
*cycle.value = 0xffff;
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = 0xff;
break;
}
return delay;
case BusDevice::IO:
switch(address) {
default:
// assert(false);
case 0x7fc000:
/* Memory controller configuration:
b0, b1: bank 1
b2, b3: bank 0
00 = 128k
01 = 512k
10 = 2mb
11 = reserved
*/
break;
case 0x7fc400: /* PSG: write to select register, read to read register. */
case 0x7fc401: /* PSG: write to write register. */
if(!cycle.data_select_active()) return delay;
advance_time(HalfCycles(2));
update_audio();
if(cycle.operation & Microcycle::Read) {
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
cycle.set_value8_high(ay_.get_data_output());
ay_.set_control_lines(GI::AY38910::ControlLines(0));
} else {
if(address == 0x7fc400) {
ay_.set_control_lines(GI::AY38910::BC1);
} else {
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
}
ay_.set_data_input(cycle.value8_high());
ay_.set_control_lines(GI::AY38910::ControlLines(0));
}
return delay + HalfCycles(2);
// The MFP block:
case 0x7ffd00: case 0x7ffd01: case 0x7ffd02: case 0x7ffd03:
case 0x7ffd04: case 0x7ffd05: case 0x7ffd06: case 0x7ffd07:
case 0x7ffd08: case 0x7ffd09: case 0x7ffd0a: case 0x7ffd0b:
case 0x7ffd0c: case 0x7ffd0d: case 0x7ffd0e: case 0x7ffd0f:
case 0x7ffd10: case 0x7ffd11: case 0x7ffd12: case 0x7ffd13:
case 0x7ffd14: case 0x7ffd15: case 0x7ffd16: case 0x7ffd17:
case 0x7ffd18: case 0x7ffd19: case 0x7ffd1a: case 0x7ffd1b:
case 0x7ffd1c: case 0x7ffd1d: case 0x7ffd1e: case 0x7ffd1f:
if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) {
cycle.set_value8_low(mfp_->read(int(address)));
} else {
mfp_->write(int(address), cycle.value8_low());
}
break;
// Video controls.
case 0x7fc100: case 0x7fc101: case 0x7fc102: case 0x7fc103:
case 0x7fc104: case 0x7fc105: case 0x7fc106: case 0x7fc107:
case 0x7fc108: case 0x7fc109: case 0x7fc10a: case 0x7fc10b:
case 0x7fc10c: case 0x7fc10d: case 0x7fc10e: case 0x7fc10f:
case 0x7fc110: case 0x7fc111: case 0x7fc112: case 0x7fc113:
case 0x7fc114: case 0x7fc115: case 0x7fc116: case 0x7fc117:
case 0x7fc118: case 0x7fc119: case 0x7fc11a: case 0x7fc11b:
case 0x7fc11c: case 0x7fc11d: case 0x7fc11e: case 0x7fc11f:
case 0x7fc120: case 0x7fc121: case 0x7fc122: case 0x7fc123:
case 0x7fc124: case 0x7fc125: case 0x7fc126: case 0x7fc127:
case 0x7fc128: case 0x7fc129: case 0x7fc12a: case 0x7fc12b:
case 0x7fc12c: case 0x7fc12d: case 0x7fc12e: case 0x7fc12f:
case 0x7fc130: case 0x7fc131:
if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(video_->read(int(address)));
} else {
video_->write(int(address), cycle.value16());
}
break;
// ACIAs.
case 0x7ffe00: case 0x7ffe01: case 0x7ffe02: case 0x7ffe03: {
// Set VPA.
mc68000_.set_is_peripheral_address(!cycle.data_select_active());
if(!cycle.data_select_active()) return delay;
const auto acia_ = (address < 0x7ffe02) ? &keyboard_acia_ : &midi_acia_;
if(cycle.operation & Microcycle::Read) {
cycle.set_value8_high((*acia_)->read(int(address)));
} else {
(*acia_)->write(int(address), cycle.value8_high());
}
} break;
// DMA.
case 0x7fc302: case 0x7fc303: case 0x7fc304: case 0x7fc305: case 0x7fc306:
if(!cycle.data_select_active()) return delay;
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(dma_->read(int(address)));
} else {
dma_->write(int(address), cycle.value16());
}
break;
}
return HalfCycles(0);
}
// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM.
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
default:
break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory[address];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory[address] >> cycle.byte_shift());
break;
case Microcycle::SelectWord:
video_.flush(); // TODO: (and below), a range check to determine whether this is really necesary.
memory[address] = cycle.value->full;
break;
case Microcycle::SelectByte:
video_.flush();
memory[address] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(memory[address] & cycle.untouched_byte_mask())
);
break;
}
return HalfCycles(0);
}
void flush() {
dma_.flush();
mfp_.flush();
keyboard_acia_.flush();
midi_acia_.flush();
video_.flush();
update_audio();
audio_queue_.perform();
}
private:
forceinline void advance_time(HalfCycles length) {
// Advance the relevant counters.
cycles_since_audio_update_ += length;
mfp_ += length;
dma_ += length;
keyboard_acia_ += length;
midi_acia_ += length;
bus_phase_ += length;
// Don't even count time for the keyboard unless it has requested it.
if(keyboard_needs_clock_) {
cycles_since_ikbd_update_ += length;
ikbd_.run_for(cycles_since_ikbd_update_.divide(HalfCycles(512)));
}
// Flush anything that needs real-time updating.
if(!may_defer_acias_) {
keyboard_acia_.flush();
midi_acia_.flush();
}
if(mfp_is_realtime_) {
mfp_.flush();
}
if(dma_is_realtime_) {
dma_.flush();
}
// Update the video output, checking whether a sequence point has been hit.
while(length >= cycles_until_video_event_) {
length -= cycles_until_video_event_;
video_ += cycles_until_video_event_;
cycles_until_video_event_ = video_->get_next_sequence_point();
mfp_->set_timer_event_input(1, video_->display_enabled());
update_interrupt_input();
}
cycles_until_video_event_ -= length;
video_ += length;
}
void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4)));
}
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
HalfCycles bus_phase_;
JustInTimeActor<Video> video_;
HalfCycles cycles_until_video_event_;
// The MFP runs at 819200/2673749ths of the CPU clock rate.
JustInTimeActor<Motorola::MFP68901::MFP68901, 819200, 2673749> mfp_;
JustInTimeActor<Motorola::ACIA::ACIA, 16> keyboard_acia_;
JustInTimeActor<Motorola::ACIA::ACIA, 16> midi_acia_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
HalfCycles cycles_since_audio_update_;
JustInTimeActor<DMAController> dma_;
HalfCycles cycles_since_ikbd_update_;
IntelligentKeyboard ikbd_;
std::vector<uint16_t> ram_;
std::vector<uint16_t> rom_;
enum class BusDevice {
MostlyRAM, RAM, ROM, Cartridge, IO, Unassigned
};
BusDevice memory_map_[256];
// MARK: - Clocking Management.
bool may_defer_acias_ = true;
bool keyboard_needs_clock_ = false;
bool mfp_is_realtime_ = false;
bool dma_is_realtime_ = false;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
// This is being called by one of the components; avoid any time flushing here as that's
// already dealt with (and, just to be absolutely sure, to avoid recursive mania).
may_defer_acias_ =
(keyboard_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime) &&
(midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime);
keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None;
mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
dma_is_realtime_ = dma_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
}
// MARK: - GPIP input.
void acia6850_did_change_interrupt_status(Motorola::ACIA::ACIA *) final {
set_gpip_input();
}
void dma_controller_did_change_output(DMAController *) final {
set_gpip_input();
// Filty hack, here! Should: set the 68000's bus request line. But until
// that's implemented, just offers magical zero-cost DMA insertion and
// extrication.
if(dma_->get_bus_request_line()) {
dma_->bus_grant(ram_.data(), ram_.size());
}
}
void set_gpip_input() {
/*
Atari ST GPIP bits:
GPIP 7: monochrome monitor detect
GPIP 6: RS-232 ring indicator
GPIP 5: FD/HD interrupt
GPIP 4: keyboard/MIDI interrupt
GPIP 3: unused
GPIP 2: RS-232 clear to send
GPIP 1: RS-232 carrier detect
GPIP 0: centronics busy
*/
mfp_->set_port_input(
0x80 | // b7: Monochrome monitor detect (1 = is monochrome).
0x40 | // b6: RS-232 ring indicator.
(dma_->get_interrupt_line() ? 0x00 : 0x20) | // b5: FD/HS interrupt (0 = interrupt requested).
((keyboard_acia_->get_interrupt_line() || midi_acia_->get_interrupt_line()) ? 0x00 : 0x10) | // b4: Keyboard/MIDI interrupt (0 = interrupt requested).
0x08 | // b3: Unused
0x04 | // b2: RS-232 clear to send.
0x02 | // b1 : RS-232 carrier detect.
0x00 // b0: Centronics busy (1 = busy).
);
}
// MARK - MFP input.
void mfp68901_did_change_interrupt_status(Motorola::MFP68901::MFP68901 *mfp) final {
update_interrupt_input();
}
void update_interrupt_input() {
if(mfp_->get_interrupt_line()) {
mc68000_.set_interrupt_level(6);
} else if(video_->vsync()) {
mc68000_.set_interrupt_level(4);
} else if(video_->hsync()) {
mc68000_.set_interrupt_level(2);
} else {
mc68000_.set_interrupt_level(0);
}
}
// MARK: - MouseMachine
Inputs::Mouse &get_mouse() final {
return ikbd_;
}
// MARK: - KeyboardMachine
void set_key_state(uint16_t key, bool is_pressed) final {
ikbd_.set_key_state(Key(key), is_pressed);
}
IntelligentKeyboard::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - AYPortHandler
void set_port_output(bool port_b, uint8_t value) final {
if(port_b) {
// TODO: ?
} else {
/*
TODO: Port A:
b7: reserved
b6: "freely usable output (monitor jack)"
b5: centronics strobe
b4: RS-232 DTR output
b3: RS-232 RTS output
b2: select floppy drive 1
b1: select floppy drive 0
b0: "page choice signal for double-sided floppy drive"
*/
dma_->set_floppy_drive_selection(!(value & 2), !(value & 4), !(value & 1));
}
}
// MARK: - MediaTarget
bool insert_media(const Analyser::Static::Media &media) final {
size_t c = 0;
for(const auto &disk: media.disks) {
dma_->set_floppy_disk(disk, c);
++c;
if(c == 2) break;
}
return true;
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
dma_->set_activity_observer(observer);
}
};
}
}
using namespace Atari::ST;
Machine *Machine::AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
return new ConcreteMachine(*target, rom_fetcher);
}
Machine::~Machine() {}

View File

@ -0,0 +1,28 @@
//
// AtariST.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef AtariST_hpp
#define AtariST_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
namespace Atari {
namespace ST {
class Machine {
public:
virtual ~Machine();
static Machine *AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}
}
#endif /* AtariST_hpp */

View File

@ -0,0 +1,232 @@
//
// DMAController.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "DMAController.hpp"
#include <cstdio>
using namespace Atari::ST;
namespace {
enum Control: uint16_t {
Direction = 0x100,
DRQSource = 0x80,
SectorCountSelect = 0x10,
CPUTarget = 0x08
};
}
DMAController::DMAController() {
fdc_.set_delegate(this);
fdc_.set_clocking_hint_observer(this);
}
uint16_t DMAController::read(int address) {
switch(address & 7) {
// Reserved.
default: break;
// Disk controller or sector count.
case 2:
if(control_ & Control::SectorCountSelect) {
return uint16_t((byte_count_ + 511) >> 9); // Assumed here: the count is of sectors remaining, i.e. it decrements
// only when a sector is complete.
} else {
if(control_ & Control::CPUTarget) {
return 0xffff;
} else {
return 0xff00 | fdc_.get_register(control_ >> 1);
}
}
break;
// DMA status.
case 3:
// TODO: should DRQ come from the HDC if that mode is selected?
return 0xfff8 | (error_ ? 0 : 1) | (byte_count_ ? 2 : 0) | (fdc_.get_data_request_line() ? 4 : 0);
// DMA addressing.
case 4: return uint16_t(0xff00 | ((address_ >> 16) & 0xff));
case 5: return uint16_t(0xff00 | ((address_ >> 8) & 0xff));
case 6: return uint16_t(0xff00 | ((address_ >> 0) & 0xff));
}
return 0xffff;
}
void DMAController::write(int address, uint16_t value) {
switch(address & 7) {
// Reserved.
default: break;
// Disk controller or sector count.
case 2:
if(control_ & Control::SectorCountSelect) {
byte_count_ = (value & 0xff) << 9; // The computer provides a sector count; that times 512 is a byte count.
// TODO: if this is a write-mode DMA operation, try to fill both buffers, ASAP.
} else {
if(control_ & Control::CPUTarget) {
// TODO: HDC.
} else {
fdc_.set_register(control_ >> 1, uint8_t(value));
}
}
break;
// DMA control; meaning is:
//
// b0: unused
// b1, b2 = address lines for FDC access.
// b3 = 1 => CPU HDC access; 0 => CPU FDC access.
// b4 = 1 => sector count access; 0 => [F/H]DC access.
// b5: unused.
// b6 = officially, 1 => DMA off; 0 => DMA on. Ignored in real hardware.
// b7 = 1 => FDC DRQs being observed; 0 => HDC access DRQs being observed.
// b8 = 1 => DMA is writing to [F/H]DC; 0 => DMA is reading. Changing value resets DMA state.
//
// All other bits: undefined.
case 3:
// Check for a DMA state reset.
if((control_^value) & Control::Direction) {
bytes_received_ = active_buffer_ = 0;
error_ = false;
byte_count_ = 0;
}
control_ = value;
break;
// DMA addressing.
case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break;
case 5: address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8)); break;
case 6: address_ = int((address_ & 0xffff00) | ((value & 0xff) << 0)); break;
}
}
void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool side2) {
fdc_.set_floppy_drive_selection(drive1, drive2, side2);
}
void DMAController::set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
fdc_.drives_[drive]->set_disk(disk);
}
void DMAController::run_for(HalfCycles duration) {
running_time_ += duration;
fdc_.run_for(duration.flush<Cycles>());
}
void DMAController::wd1770_did_change_output(WD::WD1770 *) {
// Check for a change in interrupt state.
const bool old_interrupt_line = interrupt_line_;
interrupt_line_ = fdc_.get_interrupt_request_line();
if(delegate_ && interrupt_line_ != old_interrupt_line) {
delegate_->dma_controller_did_change_output(this);
}
// Check for a change in DRQ state, if it's the FDC that is currently being watched.
if(byte_count_ && fdc_.get_data_request_line() && (control_ & Control::DRQSource)) {
--byte_count_;
if(control_ & Control::Direction) {
// TODO: DMA is supposed to be helping with a write.
} else {
// DMA is enabling a read.
// Read from the data register into the active buffer.
if(bytes_received_ < 16) {
buffer_[active_buffer_].contents[bytes_received_] = fdc_.get_register(3);
++bytes_received_;
}
if(bytes_received_ == 16) {
// Mark buffer as full.
buffer_[active_buffer_].is_full = true;
// Move to the next if it is empty; if it isn't, note a DMA error.
const auto next_buffer = active_buffer_ ^ 1;
error_ |= buffer_[next_buffer].is_full;
if(!buffer_[next_buffer].is_full) {
bytes_received_ = 0;
active_buffer_ = next_buffer;
}
// Set bus request.
if(!bus_request_line_) {
bus_request_line_ = true;
if(delegate_) delegate_->dma_controller_did_change_output(this);
}
}
}
}
}
int DMAController::bus_grant(uint16_t *ram, size_t size) {
// Being granted the bus negates the request.
bus_request_line_ = false;
if(delegate_) delegate_->dma_controller_did_change_output(this);
if(control_ & Control::Direction) {
// TODO: writes.
return 0;
} else {
// Check that the older buffer is full; stop if not.
if(!buffer_[active_buffer_ ^ 1].is_full) return 0;
for(int c = 0; c < 8; ++c) {
ram[size_t(address_ >> 1) & (size - 1)] = uint16_t(
(buffer_[active_buffer_ ^ 1].contents[(c << 1) + 0] << 8) |
(buffer_[active_buffer_ ^ 1].contents[(c << 1) + 1] << 0)
);
address_ += 2;
}
buffer_[active_buffer_ ^ 1].is_full = false;
// Check that the newer buffer is full; stop if not.
if(!buffer_[active_buffer_ ].is_full) return 8;
for(int c = 0; c < 8; ++c) {
ram[size_t(address_ >> 1) & (size - 1)] = uint16_t(
(buffer_[active_buffer_].contents[(c << 1) + 0] << 8) |
(buffer_[active_buffer_].contents[(c << 1) + 1] << 0)
);
address_ += 2;
}
buffer_[active_buffer_].is_full = false;
// Both buffers were full, so unblock reading.
bytes_received_ = 0;
return 16;
}
}
void DMAController::set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
bool DMAController::get_interrupt_line() {
return interrupt_line_;
}
bool DMAController::get_bus_request_line() {
return bus_request_line_;
}
void DMAController::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
update_clocking_observer();
}
ClockingHint::Preference DMAController::preferred_clocking() {
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
}
void DMAController::set_activity_observer(Activity::Observer *observer) {
fdc_.drives_[0]->set_activity_observer(observer, "Internal", true);
fdc_.drives_[1]->set_activity_observer(observer, "External", true);
}

View File

@ -0,0 +1,110 @@
//
// DMAController.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef DMAController_hpp
#define DMAController_hpp
#include <cstdint>
#include <vector>
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/ClockingHintSource.hpp"
#include "../../../Components/1770/1770.hpp"
#include "../../../Activity/Source.hpp"
namespace Atari {
namespace ST {
class DMAController: public WD::WD1770::Delegate, public ClockingHint::Source, public ClockingHint::Observer {
public:
DMAController();
uint16_t read(int address);
void write(int address, uint16_t value);
void run_for(HalfCycles duration);
bool get_interrupt_line();
bool get_bus_request_line();
/*!
Indicates that the DMA controller has been granted bus access to the block of memory at @c ram, which
is of size @c size.
@returns The number of words read or written.
*/
int bus_grant(uint16_t *ram, size_t size);
void set_floppy_drive_selection(bool drive1, bool drive2, bool side2);
void set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
struct Delegate {
virtual void dma_controller_did_change_output(DMAController *) = 0;
};
void set_delegate(Delegate *delegate);
void set_activity_observer(Activity::Observer *observer);
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
private:
HalfCycles running_time_;
struct WD1772: public WD::WD1770 {
WD1772(): WD::WD1770(WD::WD1770::P1772) {
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
drives_.emplace_back(new Storage::Disk::Drive(8000000, 300, 2));
set_drive(drives_[0]);
set_is_double_density(true); // TODO: is this selectable on the ST?
}
void set_motor_on(bool motor_on) final {
drives_[0]->set_motor_on(motor_on);
drives_[1]->set_motor_on(motor_on);
}
void set_floppy_drive_selection(bool drive1, bool drive2, bool side2) {
// TODO: handle no drives and/or both drives selected.
if(drive1) {
set_drive(drives_[0]);
} else {
set_drive(drives_[1]);
}
drives_[0]->set_head(side2);
drives_[1]->set_head(side2);
}
std::vector<std::shared_ptr<Storage::Disk::Drive>> drives_;
} fdc_;
void wd1770_did_change_output(WD::WD1770 *) final;
uint16_t control_ = 0;
Delegate *delegate_ = nullptr;
bool interrupt_line_ = false;
bool bus_request_line_ = false;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
// MARK: - DMA State.
struct Buffer {
uint8_t contents[16];
bool is_full = false;
} buffer_[2];
int active_buffer_ = 0;
int bytes_received_ = 0;
bool error_ = false;
int address_ = 0;
int byte_count_ = 0;
};
}
}
#endif /* DMAController_hpp */

View File

@ -0,0 +1,354 @@
//
// IntelligentKeyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/11/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "IntelligentKeyboard.hpp"
using namespace Atari::ST;
IntelligentKeyboard::IntelligentKeyboard(Serial::Line &input, Serial::Line &output) : output_line_(output) {
input.set_read_delegate(this, Storage::Time(2, 15625));
output_line_.set_writer_clock_rate(15625);
mouse_button_state_ = 0;
mouse_movement_[0] = 0;
mouse_movement_[1] = 0;
}
bool IntelligentKeyboard::serial_line_did_produce_bit(Serial::Line *, int bit) {
// Shift.
command_ = (command_ >> 1) | (bit << 9);
// If that's 10 bits, decode a byte and stop.
bit_count_ = (bit_count_ + 1) % 10;
if(!bit_count_) {
dispatch_command(uint8_t(command_ >> 1));
command_ = 0;
return false;
}
// Continue.
return true;
}
ClockingHint::Preference IntelligentKeyboard::preferred_clocking() {
return output_line_.transmission_data_time_remaining().as_integral() ? ClockingHint::Preference::RealTime : ClockingHint::Preference::None;
}
void IntelligentKeyboard::run_for(HalfCycles duration) {
// Take this opportunity to check for mouse and keyboard events,
// which will have been received asynchronously.
if(mouse_mode_ == MouseMode::Relative) {
const int captured_movement[2] = { mouse_movement_[0].load(), mouse_movement_[1].load() };
const int captured_button_state = mouse_button_state_;
if(
(posted_button_state_ != captured_button_state) ||
(abs(captured_movement[0]) >= mouse_threshold_[0]) ||
(abs(captured_movement[1]) >= mouse_threshold_[1]) ) {
mouse_movement_[0] -= captured_movement[0];
mouse_movement_[1] -= captured_movement[1];
post_relative_mouse_event(captured_movement[0], captured_movement[1]);
}
} else {
}
// Forward key changes; implicit assumption here: mutexs are cheap while there's
// negligible contention.
{
std::lock_guard<decltype(key_queue_mutex_)> guard(key_queue_mutex_);
for(uint8_t key: key_queue_) {
output_bytes({key});
}
key_queue_.clear();
}
output_line_.advance_writer(duration);
}
void IntelligentKeyboard::output_bytes(std::initializer_list<uint8_t> values) {
// Wrap the value in a start and stop bit, and send it on its way.
for(auto value : values) {
output_line_.write(2, 10, 0x200 | (value << 1));
}
update_clocking_observer();
}
void IntelligentKeyboard::dispatch_command(uint8_t command) {
// Enqueue for parsing.
command_sequence_.push_back(command);
// For each possible command, check that the proper number of bytes are present.
// If not, exit. If so, perform and drop out of the switch.
switch(command_sequence_.front()) {
default:
printf("Unrecognised IKBD command %02x\n", command);
break;
case 0x80:
/*
Reset: 0x80 0x01.
"Any byte following an 0x80 command byte other than 0x01 is ignored (and causes the 0x80 to be ignored)."
*/
if(command_sequence_.size() != 2) return;
if(command_sequence_[1] == 0x01) {
reset();
}
break;
case 0x07:
if(command_sequence_.size() != 2) return;
set_mouse_button_actions(command_sequence_[1]);
break;
case 0x08:
set_relative_mouse_position_reporting();
break;
case 0x09:
if(command_sequence_.size() != 5) return;
set_absolute_mouse_position_reporting(
uint16_t((command_sequence_[1] << 8) | command_sequence_[2]),
uint16_t((command_sequence_[3] << 8) | command_sequence_[4])
);
break;
case 0x0a:
if(command_sequence_.size() != 3) return;
set_mouse_keycode_reporting(command_sequence_[1], command_sequence_[2]);
break;
case 0x0b:
if(command_sequence_.size() != 3) return;
set_mouse_threshold(command_sequence_[1], command_sequence_[2]);
break;
case 0x0c:
if(command_sequence_.size() != 3) return;
set_mouse_scale(command_sequence_[1], command_sequence_[2]);
break;
case 0x0d:
interrogate_mouse_position();
break;
case 0x0e:
if(command_sequence_.size() != 6) return;
/* command_sequence_[1] has no defined meaning. */
set_mouse_position(
uint16_t((command_sequence_[2] << 8) | command_sequence_[3]),
uint16_t((command_sequence_[4] << 8) | command_sequence_[5])
);
break;
case 0x0f: set_mouse_y_upward(); break;
case 0x10: set_mouse_y_downward(); break;
case 0x11: resume(); break;
case 0x12: disable_mouse(); break;
case 0x13: pause(); break;
case 0x14: set_joystick_event_mode(); break;
case 0x15: set_joystick_interrogation_mode(); break;
case 0x16: interrogate_joysticks(); break;
case 0x1a: disable_joysticks(); break;
}
// There was no premature exit, so a complete command sequence must have been satisfied.
command_sequence_.clear();
}
void IntelligentKeyboard::reset() {
// Reset should perform a self test, lasting at most 200ms, then post 0xf0.
// Following that it should look for any keys that currently seem to be pressed.
// Those are considered stuck and a break code is generated for them.
output_bytes({0xf0});
}
void IntelligentKeyboard::resume() {
}
void IntelligentKeyboard::pause() {
}
void IntelligentKeyboard::disable_mouse() {
}
void IntelligentKeyboard::set_relative_mouse_position_reporting() {
}
void IntelligentKeyboard::set_absolute_mouse_position_reporting(uint16_t max_x, uint16_t max_y) {
}
void IntelligentKeyboard::set_mouse_position(uint16_t x, uint16_t y) {
}
void IntelligentKeyboard::set_mouse_keycode_reporting(uint8_t delta_x, uint8_t delta_y) {
}
void IntelligentKeyboard::set_mouse_threshold(uint8_t x, uint8_t y) {
}
void IntelligentKeyboard::set_mouse_scale(uint8_t x, uint8_t y) {
}
void IntelligentKeyboard::set_mouse_y_downward() {
}
void IntelligentKeyboard::set_mouse_y_upward() {
}
void IntelligentKeyboard::set_mouse_button_actions(uint8_t actions) {
}
void IntelligentKeyboard::interrogate_mouse_position() {
output_bytes({
0xf7, // Beginning of mouse response.
0x00, // 0000dcba; a = right button down since last interrogation, b = right button up since, c/d = left button.
0x00, // x motion: MSB, LSB
0x00,
0x00, // y motion: MSB, LSB
0x00
});
}
void IntelligentKeyboard::post_relative_mouse_event(int x, int y) {
posted_button_state_ = mouse_button_state_;
// Break up the motion to impart, if it's too large.
do {
int stepped_motion[2] = {
(x >= -128 && x < 127) ? x : (x > 0 ? 127 : -128),
(y >= -128 && y < 127) ? y : (y > 0 ? 127 : -128),
};
output_bytes({
uint8_t(0xf8 | posted_button_state_), // Command code is a function of button state.
uint8_t(stepped_motion[0]),
uint8_t(stepped_motion[1]),
});
x -= stepped_motion[0];
y -= stepped_motion[1];
} while(x || y);
}
// MARK: - Keyboard Input
void IntelligentKeyboard::set_key_state(Key key, bool is_pressed) {
std::lock_guard<decltype(key_queue_mutex_)> guard(key_queue_mutex_);
if(is_pressed) {
key_queue_.push_back(uint8_t(key));
} else {
key_queue_.push_back(0x80 | uint8_t(key));
}
}
uint16_t IntelligentKeyboard::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
using Key = Inputs::Keyboard::Key;
using STKey = Atari::ST::Key;
switch(key) {
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
#define Bind(x, y) case Key::x: return uint16_t(STKey::y)
#define QBind(x) case Key::x: return uint16_t(STKey::x)
QBind(k1); QBind(k2); QBind(k3); QBind(k4); QBind(k5); QBind(k6); QBind(k7); QBind(k8); QBind(k9); QBind(k0);
QBind(Q); QBind(W); QBind(E); QBind(R); QBind(T); QBind(Y); QBind(U); QBind(I); QBind(O); QBind(P);
QBind(A); QBind(S); QBind(D); QBind(F); QBind(G); QBind(H); QBind(J); QBind(K); QBind(L);
QBind(Z); QBind(X); QBind(C); QBind(V); QBind(B); QBind(N); QBind(M);
QBind(Left); QBind(Right); QBind(Up); QBind(Down);
QBind(BackTick); QBind(Tab);
QBind(Hyphen); QBind(Equals); QBind(Backspace);
QBind(OpenSquareBracket);
QBind(CloseSquareBracket);
QBind(CapsLock);
QBind(Semicolon);
QBind(Quote);
Bind(Enter, Return);
QBind(LeftShift);
QBind(RightShift);
Bind(Comma, Comma);
Bind(FullStop, FullStop);
Bind(ForwardSlash, ForwardSlash);
Bind(LeftOption, Alt);
Bind(RightOption, Alt);
QBind(Space);
QBind(Backslash);
/* Bind(KeyPadDelete, KeyPadDelete);
Bind(KeyPadEquals, KeyPadEquals);
Bind(KeyPadSlash, KeyPadSlash);
Bind(KeyPadAsterisk, KeyPadAsterisk);
Bind(KeyPadMinus, KeyPadMinus);
Bind(KeyPadPlus, KeyPadPlus);
Bind(KeyPadEnter, KeyPadEnter);
Bind(KeyPadDecimalPoint, KeyPadDecimalPoint);
Bind(KeyPad9, KeyPad9);
Bind(KeyPad8, KeyPad8);
Bind(KeyPad7, KeyPad7);
Bind(KeyPad6, KeyPad6);
Bind(KeyPad5, KeyPad5);
Bind(KeyPad4, KeyPad4);
Bind(KeyPad3, KeyPad3);
Bind(KeyPad2, KeyPad2);
Bind(KeyPad1, KeyPad1);
Bind(KeyPad0, KeyPad0);*/
#undef QBind
#undef Bind
}
}
// MARK: - Mouse Input
void IntelligentKeyboard::move(int x, int y) {
mouse_movement_[0] += x;
mouse_movement_[1] += y;
}
int IntelligentKeyboard::get_number_of_buttons() {
return 2;
}
void IntelligentKeyboard::set_button_pressed(int index, bool is_pressed) {
const auto mask = 1 << (index ^ 1); // The primary button is b1; the secondary is b0.
if(is_pressed) {
mouse_button_state_ |= mask;
} else {
mouse_button_state_ &= ~mask;
}
}
void IntelligentKeyboard::reset_all_buttons() {
mouse_button_state_ = 0;
}
// MARK: - Joystick Output
void IntelligentKeyboard::disable_joysticks() {
joystick_mode_ = JoystickMode::Disabled;
}
void IntelligentKeyboard::set_joystick_event_mode() {
joystick_mode_ = JoystickMode::Event;
}
void IntelligentKeyboard::set_joystick_interrogation_mode() {
joystick_mode_ = JoystickMode::Interrogation;
}
void IntelligentKeyboard::interrogate_joysticks() {
output_bytes({
0xfd,
0x00,
0x00
});
}

View File

@ -0,0 +1,136 @@
//
// IntelligentKeyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/11/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef IntelligentKeyboard_hpp
#define IntelligentKeyboard_hpp
#include "../../../ClockReceiver/ClockingHintSource.hpp"
#include "../../../Components/Serial/Line.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../../Inputs/Mouse.hpp"
#include <atomic>
#include <mutex>
namespace Atari {
namespace ST {
enum class Key: uint16_t {
Escape = 1,
k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, Backspace,
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, Return,
Control, A, S, D, F, G, H, J, K, L, Semicolon, Quote, BackTick,
LeftShift, Backslash, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
/* 0x37 is unused. */
Alt = 0x38, Space, CapsLock, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10,
/* Various gaps follow. */
Home = 0x47, Up,
KeypadMinus = 0x4a, Left,
Right = 0x4d, KeypadPlus,
Down = 0x50,
Insert = 0x52, Delete,
ISO = 0x60, Undo, Help, KeypadOpenBracket, KeypadCloseBracket, KeypadDivide, KeypadMultiply,
Keypad7, Keypad8, Keypad9, Keypad4, KeyPad5, Keypad6, Keypad1, Keypad2, Keypad3, Keypad0, KeypadDecimalPoint,
KeypadEnter
};
static_assert(uint16_t(Key::RightShift) == 0x36, "RightShift should have key code 0x36; check intermediate entries");
static_assert(uint16_t(Key::F10) == 0x44, "F10 should have key code 0x44; check intermediate entries");
static_assert(uint16_t(Key::KeypadEnter) == 0x72, "KeypadEnter should have key code 0x72; check intermediate entries");
/*!
A receiver for the Atari ST's "intelligent keyboard" commands, which actually cover
keyboard input and output and mouse handling.
*/
class IntelligentKeyboard:
public Serial::Line::ReadDelegate,
public ClockingHint::Source,
public Inputs::Mouse {
public:
IntelligentKeyboard(Serial::Line &input, Serial::Line &output);
ClockingHint::Preference preferred_clocking() final;
void run_for(HalfCycles duration);
void set_key_state(Key key, bool is_pressed);
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
};
private:
// MARK: - Key queue.
std::mutex key_queue_mutex_;
std::vector<uint8_t> key_queue_;
// MARK: - Serial line state.
int bit_count_ = 0;
int command_ = 0;
Serial::Line &output_line_;
void output_bytes(std::initializer_list<uint8_t> value);
bool serial_line_did_produce_bit(Serial::Line *, int bit) final;
// MARK: - Command dispatch.
std::vector<uint8_t> command_sequence_;
void dispatch_command(uint8_t command);
// MARK: - Flow control.
void reset();
void resume();
void pause();
// MARK: - Mouse.
void disable_mouse();
void set_relative_mouse_position_reporting();
void set_absolute_mouse_position_reporting(uint16_t max_x, uint16_t max_y);
void set_mouse_position(uint16_t x, uint16_t y);
void set_mouse_keycode_reporting(uint8_t delta_x, uint8_t delta_y);
void set_mouse_threshold(uint8_t x, uint8_t y);
void set_mouse_scale(uint8_t x, uint8_t y);
void set_mouse_y_downward();
void set_mouse_y_upward();
void set_mouse_button_actions(uint8_t actions);
void interrogate_mouse_position();
// Inputs::Mouse.
void move(int x, int y) final;
int get_number_of_buttons() final;
void set_button_pressed(int index, bool is_pressed) final;
void reset_all_buttons() final;
enum class MouseMode {
Relative, Absolute
} mouse_mode_ = MouseMode::Relative;
// Absolute positioning state.
int mouse_range_[2] = {0, 0};
int mouse_scale_[2] = {0, 0};
// Relative positioning state.
int posted_button_state_ = 0;
int mouse_threshold_[2] = {1, 1};
void post_relative_mouse_event(int x, int y);
// Received mouse state.
std::atomic<int> mouse_movement_[2];
std::atomic<int> mouse_button_state_;
// MARK: - Joystick.
void disable_joysticks();
void set_joystick_event_mode();
void set_joystick_interrogation_mode();
void interrogate_joysticks();
enum class JoystickMode {
Disabled, Event, Interrogation
} joystick_mode_ = JoystickMode::Event;
};
}
}
#endif /* IntelligentKeyboard_hpp */

500
Machines/Atari/ST/Video.cpp Normal file
View File

@ -0,0 +1,500 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
using namespace Atari::ST;
namespace {
/*!
Defines the line counts at which mode-specific events will occur:
vertical enable being set and being reset, and the line on which
the frame will end.
*/
const struct VerticalParams {
const int set_enable;
const int reset_enable;
const int height;
} vertical_params[3] = {
{63, 263, 313}, // 47 rather than 63 on early machines.
{34, 234, 263},
{1, 401, 500} // 72 Hz mode: who knows?
};
/// @returns The correct @c VerticalParams for output at @c frequency.
const VerticalParams &vertical_parameters(FieldFrequency frequency) {
return vertical_params[int(frequency)];
}
/*!
Defines the horizontal counts at which mode-specific events will occur:
horizontal enable being set and being reset, blank being set and reset, and the
intended length of this ine.
The caller should:
* latch line length at cycle 54 (TODO: also for 72Hz mode?);
* at (line length - 50), start sync and reset enable (usually for the second time);
* at (line length - 10), disable sync.
*/
const struct HorizontalParams {
const int set_enable;
const int reset_enable;
const int set_blank;
const int reset_blank;
const int length;
} horizontal_params[3] = {
{56*2, 376*2, 450*2, 28*2, 512*2},
{52*2, 372*2, 450*2, 24*2, 508*2},
{4*2, 164*2, 184*2, 2*2, 224*2}
};
const HorizontalParams &horizontal_parameters(FieldFrequency frequency) {
return horizontal_params[int(frequency)];
}
#ifndef NDEBUG
struct Checker {
Checker() {
for(int c = 0; c < 3; ++c) {
// Expected horizontal order of events: reset blank, enable display, disable display, enable blank (at least 50 before end of line), end of line
const auto horizontal = horizontal_parameters(FieldFrequency(c));
assert(horizontal.reset_blank < horizontal.set_enable);
assert(horizontal.set_enable < horizontal.reset_enable);
assert(horizontal.reset_enable < horizontal.set_blank);
assert(horizontal.set_blank+50 < horizontal.length);
// Expected vertical order of events: reset blank, enable display, disable display, enable blank (at least 50 before end of line), end of line
const auto vertical = vertical_parameters(FieldFrequency(c));
assert(vertical.set_enable < vertical.reset_enable);
assert(vertical.reset_enable < vertical.height);
}
}
} checker;
#endif
}
Video::Video() :
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4) {
// Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's
// usual output height of 200 lines.
crt_.set_visible_area(crt_.get_rect_for_area(33, 260, 188, 850, 4.0f / 3.0f));
}
void Video::set_ram(uint16_t *ram, size_t size) {
ram_ = ram;
}
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
void Video::run_for(HalfCycles duration) {
const auto horizontal_timings = horizontal_parameters(field_frequency_);
const auto vertical_timings = vertical_parameters(field_frequency_);
int integer_duration = int(duration.as_integral());
while(integer_duration) {
// Seed next event to end of line.
int next_event = line_length_;
// Check the explicitly-placed events.
if(horizontal_timings.reset_blank > x_) next_event = std::min(next_event, horizontal_timings.reset_blank);
if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank);
if(horizontal_timings.reset_enable > x_) next_event = std::min(next_event, horizontal_timings.reset_enable);
if(horizontal_timings.set_enable > x_) next_event = std::min(next_event, horizontal_timings.set_enable);
// Check for events that are relative to existing latched state.
if(line_length_ - 50*2 > x_) next_event = std::min(next_event, line_length_ - 50*2);
if(line_length_ - 10*2 > x_) next_event = std::min(next_event, line_length_ - 10*2);
// Also, a vertical sync event might intercede.
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < 30*2 && next_event >= 30*2) {
next_event = 30*2;
}
// Determine current output mode and number of cycles to output for.
const int run_length = std::min(integer_duration, next_event - x_);
enum class OutputMode {
Sync, Blank, Border, Pixels
} output_mode;
if(horizontal_.sync || vertical_.sync) {
// Output sync.
output_mode = OutputMode::Sync;
} else if(horizontal_.blank || vertical_.blank) {
// Output blank.
output_mode = OutputMode::Blank;
} else if(!vertical_.enable) {
// There can be no pixels this line, just draw border.
output_mode = OutputMode::Border;
} else {
output_mode = horizontal_.enable ? OutputMode::Pixels : OutputMode::Border;
}
switch(output_mode) {
case OutputMode::Sync:
pixel_buffer_.flush(crt_);
crt_.output_sync(run_length);
break;
case OutputMode::Blank:
data_latch_position_ = 0;
pixel_buffer_.flush(crt_);
crt_.output_blank(run_length);
break;
case OutputMode::Border: {
if(!output_shifter_) {
pixel_buffer_.flush(crt_);
output_border(run_length);
} else {
if(run_length < 32) {
shift_out(run_length); // TODO: this might end up overrunning.
if(!output_shifter_) pixel_buffer_.flush(crt_);
} else {
shift_out(32);
output_shifter_ = 0;
pixel_buffer_.flush(crt_);
output_border(run_length - 32);
}
}
} break;
default: {
// There will be pixels this line, subject to the shifter pipeline.
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
// and during the rest of the window, shift out.
int start_column = x_ >> 3;
const int end_column = (x_ + run_length) >> 3;
// Rules obeyed below:
//
// Video fetches occur as the first act of business in a column. Each
// fetch is then followed by 8 shift clocks. Whether or not the shifter
// was reloaded by the fetch depends on the FIFO.
if(start_column == end_column) {
shift_out(run_length);
} else {
// Continue the current column if partway across.
if(x_&7) {
// If at least one column boundary is crossed, complete this column.
// Otherwise the run_length is clearly less than 8 and within this column,
// so go for the entirety of it.
shift_out(8 - (x_ & 7));
++start_column;
latch_word();
}
// Run for all columns that have their starts in this time period.
int complete_columns = end_column - start_column;
while(complete_columns--) {
shift_out(8);
latch_word();
}
// Output the start of the next column, if necessary.
if(start_column != end_column && (x_ + run_length) & 7) {
shift_out((x_ + run_length) & 7);
}
}
} break;
}
// Check for whether line length should have been latched during this run.
if(x_ <= 54*2 && (x_ + run_length) > 54*2) line_length_ = horizontal_timings.length;
// Make a decision about vertical state on cycle 502.
if(x_ <= 502*2 && (x_ + run_length) > 502*2) {
next_y_ = y_ + 1;
next_vertical_ = vertical_;
next_vertical_.sync_schedule = VerticalState::SyncSchedule::None;
// Use vertical_parameters to get parameters for the current output frequency.
if(next_y_ == vertical_timings.set_enable) {
next_vertical_.enable = true;
} else if(next_y_ == vertical_timings.reset_enable) {
next_vertical_.enable = false;
} else if(next_y_ == vertical_timings.height) {
next_y_ = 0;
next_vertical_.sync_schedule = VerticalState::SyncSchedule::Begin;
current_address_ = base_address_ >> 1;
} else if(next_y_ == 3) {
next_vertical_.sync_schedule = VerticalState::SyncSchedule::End;
}
}
// Apply the next event.
x_ += run_length;
integer_duration -= run_length;
// Check horizontal events.
if(horizontal_timings.reset_blank == x_) horizontal_.blank = false;
else if(horizontal_timings.set_blank == x_) horizontal_.blank = true;
else if(horizontal_timings.reset_enable == x_) horizontal_.enable = false;
else if(horizontal_timings.set_enable == x_) horizontal_.enable = true;
else if(line_length_ - 50*2 == x_) horizontal_.sync = true;
else if(line_length_ - 10*2 == x_) horizontal_.sync = false;
// Check vertical events.
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == 30*2) {
vertical_.sync = vertical_.sync_schedule == VerticalState::SyncSchedule::Begin;
vertical_.enable &= !vertical_.sync;
}
// Check whether the terminating event was end-of-line; if so then advance
// the vertical bits of state.
if(x_ == line_length_) {
x_ = 0;
vertical_ = next_vertical_;
y_ = next_y_;
}
}
}
void Video::latch_word() {
data_latch_[data_latch_position_] = ram_[current_address_ & 262143];
++current_address_;
++data_latch_position_;
if(data_latch_position_ == 4) {
data_latch_position_ = 0;
output_shifter_ =
(uint64_t(data_latch_[0]) << 48) |
(uint64_t(data_latch_[1]) << 32) |
(uint64_t(data_latch_[2]) << 16) |
uint64_t(data_latch_[3]);
}
}
void Video::shift_out(int length) {
if(pixel_buffer_.output_bpp != output_bpp_) {
pixel_buffer_.flush(crt_);
}
if(!pixel_buffer_.pixel_pointer) {
pixel_buffer_.allocate(crt_);
pixel_buffer_.output_bpp = output_bpp_;
}
pixel_buffer_.cycles_output += length;
switch(output_bpp_) {
case OutputBpp::One: {
int pixels = length << 1;
pixel_buffer_.pixels_output += pixels;
if(pixel_buffer_.pixel_pointer) {
while(pixels--) {
*pixel_buffer_.pixel_pointer = ((output_shifter_ >> 63) & 1) * 0xffff;
output_shifter_ <<= 1;
++pixel_buffer_.pixel_pointer;
}
} else {
output_shifter_ <<= pixels;
}
} break;
case OutputBpp::Two: {
pixel_buffer_.pixels_output += length;
#if TARGET_RT_BIG_ENDIAN
const int upper = 0;
#else
const int upper = 1;
#endif
if(pixel_buffer_.pixel_pointer) {
while(length--) {
*pixel_buffer_.pixel_pointer = palette_[
((output_shifter_ >> 63) & 1) |
((output_shifter_ >> 46) & 2)
];
// This ensures that the top two words shift one to the left;
// their least significant bits are fed from the most significant bits
// of the bottom two words, respectively.
shifter_halves_[upper] = (shifter_halves_[upper] << 1) & 0xfffefffe;
shifter_halves_[upper] |= (shifter_halves_[upper^1] & 0x80008000) >> 15;
shifter_halves_[upper^1] = (shifter_halves_[upper^1] << 1) & 0xfffefffe;
++pixel_buffer_.pixel_pointer;
}
} else {
while(length--) {
shifter_halves_[upper] = (shifter_halves_[upper] << 1) & 0xfffefffe;
shifter_halves_[upper] |= (shifter_halves_[upper^1] & 0x80008000) >> 15;
shifter_halves_[upper^1] = (shifter_halves_[upper^1] << 1) & 0xfffefffe;
}
}
} break;
default:
case OutputBpp::Four:
assert(!(length & 1));
pixel_buffer_.pixels_output += length >> 1;
if(pixel_buffer_.pixel_pointer) {
while(length) {
*pixel_buffer_.pixel_pointer = palette_[
((output_shifter_ >> 63) & 1) |
((output_shifter_ >> 46) & 2) |
((output_shifter_ >> 29) & 4) |
((output_shifter_ >> 12) & 8)
];
output_shifter_ = (output_shifter_ << 1) & 0xfffefffefffefffe;
++pixel_buffer_.pixel_pointer;
length -= 2;
}
} else {
while(length) {
output_shifter_ = (output_shifter_ << 1) & 0xfffefffefffefffe;
length -= 2;
}
}
break;
}
// Check for buffer being full. Buffers are allocated as 328 pixels, and this method is
// never called for more than 8 pixels, so there's no chance of overrun.
if(pixel_buffer_.pixel_pointer && pixel_buffer_.pixels_output >= 320)
pixel_buffer_.flush(crt_);
}
void Video::output_border(int duration) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = palette_[0];
crt_.output_level(duration);
}
bool Video::hsync() {
return horizontal_.sync;
}
bool Video::vsync() {
return vertical_.sync;
}
bool Video::display_enabled() {
return horizontal_.enable && vertical_.enable;
}
HalfCycles Video::get_next_sequence_point() {
// The next sequence point will be whenever display_enabled, vsync or hsync next changes.
// Sequence of events within a standard line:
//
// 1) blank disabled;
// 2) display enabled;
// 3) display disabled;
// 4) blank enabled;
// 5) sync enabled;
// 6) sync disabled;
// 7) end-of-line, potential vertical event.
//
// If this line has a vertical sync event on it, there will also be an event at cycle 30,
// which will always falls somewhere between (1) and (4) but might or might not be in the
// visible area.
const auto horizontal_timings = horizontal_parameters(field_frequency_);
// const auto vertical_timings = vertical_parameters(field_frequency_);
// If this is a vertically-enabled line, check for the display enable boundaries.
if(vertical_.enable) {
// TODO: what if there's a sync event scheduled for this line?
if(x_ < horizontal_timings.set_enable) return HalfCycles(horizontal_timings.set_enable - x_);
if(x_ < horizontal_timings.reset_enable) return HalfCycles(horizontal_timings.reset_enable - x_);
} else {
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && (x_ < 30*2)) {
return HalfCycles(30*2 - x_);
}
}
// Test for beginning and end of sync.
if(x_ < line_length_ - 50) return HalfCycles(line_length_ - 50 - x_);
if(x_ < line_length_ - 10) return HalfCycles(line_length_ - 10 - x_);
// Okay, then, it depends on the next line. If the next line is the start or end of vertical sync,
// it's that.
// if(y_+1 == vertical_timings.height || y_+1 == 3) return HalfCycles(line_length_ - x_);
// It wasn't any of those, so as a temporary expedient, just supply end of line.
return HalfCycles(line_length_ - x_);
}
// MARK: - IO dispatch
uint16_t Video::read(int address) {
address &= 0x3f;
switch(address) {
default:
break;
case 0x00: return uint16_t(0xff00 | (base_address_ >> 16));
case 0x01: return uint16_t(0xff00 | (base_address_ >> 8));
case 0x02: return uint16_t(0xff00 | (current_address_ >> 15)); // Current address is kept in word precision internally;
case 0x03: return uint16_t(0xff00 | (current_address_ >> 7)); // the shifts here represent a conversion back to
case 0x04: return uint16_t(0xff00 | (current_address_ << 1)); // byte precision.
case 0x05: return sync_mode_ | 0xfcff;
case 0x30: return video_mode_ | 0xfcff;
case 0x20: case 0x21: case 0x22: case 0x23:
case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2a: case 0x2b:
case 0x2c: case 0x2d: case 0x2e: case 0x2f: return raw_palette_[address - 0x20];
}
return 0xff;
}
void Video::write(int address, uint16_t value) {
address &= 0x3f;
switch(address) {
default: break;
// Start address.
case 0x00: base_address_ = (base_address_ & 0x00ffff) | ((value & 0xff) << 16); break;
case 0x01: base_address_ = (base_address_ & 0xff00ff) | ((value & 0xff) << 8); break;
// Sync mode and pixel mode.
case 0x05:
sync_mode_ = value;
update_output_mode();
break;
case 0x30:
video_mode_ = value;
update_output_mode();
break;
// Palette.
case 0x20: case 0x21: case 0x22: case 0x23:
case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2a: case 0x2b:
case 0x2c: case 0x2d: case 0x2e: case 0x2f: {
raw_palette_[address - 0x20] = value;
uint8_t *const entry = reinterpret_cast<uint8_t *>(&palette_[address - 0x20]);
entry[0] = uint8_t((value & 0x700) >> 7);
entry[1] = uint8_t((value & 0x77) << 1);
} break;
}
}
void Video::update_output_mode() {
// If this is black and white mode, that's that.
switch((video_mode_ >> 8) & 3) {
default:
case 0: output_bpp_ = OutputBpp::Four; break;
case 1: output_bpp_ = OutputBpp::Two; break;
// 1bpp mode ignores the otherwise-programmed frequency.
case 2:
output_bpp_ = OutputBpp::One;
field_frequency_ = FieldFrequency::SeventyTwo;
return;
}
field_frequency_ = (sync_mode_ & 0x200) ? FieldFrequency::Fifty : FieldFrequency::Sixty;
}

126
Machines/Atari/ST/Video.hpp Normal file
View File

@ -0,0 +1,126 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Atari_ST_Video_hpp
#define Atari_ST_Video_hpp
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace Atari {
namespace ST {
enum class FieldFrequency {
Fifty = 0, Sixty = 1, SeventyTwo = 2
};
class Video {
public:
Video();
/*!
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);
/*!
@returns the number of cycles until there is next a change in the hsync,
vsync or display_enable outputs.
*/
HalfCycles get_next_sequence_point();
bool hsync();
bool vsync();
bool display_enabled();
void set_ram(uint16_t *, size_t size);
uint16_t read(int address);
void write(int address, uint16_t value);
private:
Outputs::CRT::CRT crt_;
uint16_t raw_palette_[16];
uint16_t palette_[16];
int base_address_ = 0;
int current_address_ = 0;
uint16_t *ram_;
uint16_t line_buffer_[256];
int x_ = 0, y_ = 0, next_y_ = 0;
void output_border(int duration);
uint16_t video_mode_ = 0;
uint16_t sync_mode_ = 0;
FieldFrequency field_frequency_ = FieldFrequency::Fifty;
enum class OutputBpp {
One, Two, Four
} output_bpp_;
void update_output_mode();
struct HorizontalState {
bool enable = false;
bool blank = false;
bool sync = false;
} horizontal_;
struct VerticalState {
bool enable = false;
bool blank = false;
enum class SyncSchedule {
/// No sync events this line.
None,
/// Sync should begin during this horizontal line.
Begin,
/// Sync should end during this horizontal line.
End,
} sync_schedule = SyncSchedule::None;
bool sync = false;
} vertical_, next_vertical_;
int line_length_ = 1024;
int data_latch_position_ = 0;
uint16_t data_latch_[4];
union {
uint64_t output_shifter_;
uint32_t shifter_halves_[2];
};
void shift_out(int length);
void latch_word();
struct PixelBufferState {
uint16_t *pixel_pointer;
int pixels_output = 0;
int cycles_output = 0;
OutputBpp output_bpp;
void flush(Outputs::CRT::CRT &crt) {
if(cycles_output) {
crt.output_data(cycles_output, size_t(pixels_output));
pixels_output = cycles_output = 0;
pixel_pointer = nullptr;
}
}
void allocate(Outputs::CRT::CRT &crt) {
flush(crt);
pixel_pointer = reinterpret_cast<uint16_t *>(crt.begin_data(320 + 32));
}
} pixel_buffer_;
};
}
}
#endif /* Atari_ST_Video_hpp */

View File

@ -397,7 +397,7 @@ class ConcreteMachine:
}
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
JustInTimeActor<TI::TMS::TMS9918, 1, 1, HalfCycles> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;

View File

@ -166,7 +166,7 @@ class ConcreteMachine:
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions
cycles += video_output_.get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1);
cycles += video_output_.get_cycles_until_next_ram_availability(int(cycles_since_display_update_.as_integral()) + 1);
} else {
switch(address & 0xff0f) {
case 0xfe00:

View File

@ -80,7 +80,7 @@ void Tape::run_for(const Cycles cycles) {
TapePlayer::run_for(cycles);
}
} else {
output_.cycles_into_pulse += static_cast<unsigned int>(cycles.as_int());
output_.cycles_into_pulse += static_cast<unsigned int>(cycles.as_integral());
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
push_tape_bit(1);

View File

@ -225,7 +225,7 @@ void VideoOutput::output_pixels(int number_of_cycles) {
}
void VideoOutput::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
int number_of_cycles = int(cycles.as_integral());
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;

View File

@ -53,7 +53,7 @@ void DiskROM::run_for(HalfCycles half_cycles) {
// Input clock is going to be 7159090/2 Mhz, but the drive controller
// needs an 8Mhz clock, so scale up. 8000000/7159090 simplifies to
// 800000/715909.
controller_cycles_ += 800000 * half_cycles.as_int();
controller_cycles_ += 800000 * half_cycles.as_integral();
WD::WD1770::run_for(Cycles(static_cast<int>(controller_cycles_ / 715909)));
controller_cycles_ %= 715909;
}

View File

@ -599,7 +599,7 @@ class ConcreteMachine:
}
if(!tape_player_is_sleeping_)
tape_player_.run_for(cycle.length.as_int());
tape_player_.run_for(int(cycle.length.as_integral()));
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= total_length;
@ -752,7 +752,7 @@ class ConcreteMachine:
};
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
JustInTimeActor<TI::TMS::TMS9918> vdp_;
Intel::i8255::i8255<i8255PortHandler> i8255_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;

View File

@ -414,7 +414,7 @@ class ConcreteMachine:
Target::Region region_;
Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
JustInTimeActor<TI::TMS::TMS9918> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;

View File

@ -15,7 +15,7 @@ namespace {
// by comparing the amount of time this emulator took to show a directory versus a video of
// a real Oric. It therefore assumes all other timing measurements were correct on the day
// of the test. More work to do, I think.
const int head_load_request_counter_target = 7653333;
const Cycles::IntType head_load_request_counter_target = 7653333;
}
Microdisc::Microdisc() : WD1770(P1793) {
@ -32,7 +32,7 @@ void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive
}
void Microdisc::set_control_register(uint8_t control) {
uint8_t changes = last_control_ ^ control;
const uint8_t changes = last_control_ ^ control;
last_control_ = control;
set_control_register(control, changes);
}
@ -48,7 +48,7 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b4: side select
if(changes & 0x10) {
int head = (control & 0x10) ? 1 : 0;
const int head = (control & 0x10) ? 1 : 0;
for(auto &drive : drives_) {
if(drive) drive->set_head(head);
}
@ -61,9 +61,9 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b0: IRQ enable
if(changes & 0x01) {
bool had_irq = get_interrupt_request_line();
const bool had_irq = get_interrupt_request_line();
irq_enable_ = !!(control & 0x01);
bool has_irq = get_interrupt_request_line();
const bool has_irq = get_interrupt_request_line();
if(has_irq != had_irq && delegate_) {
delegate_->wd1770_did_change_output(this);
}
@ -114,7 +114,7 @@ void Microdisc::set_head_load_request(bool head_load) {
void Microdisc::run_for(const Cycles cycles) {
if(head_load_request_counter_ < head_load_request_counter_target) {
head_load_request_counter_ += cycles.as_int();
head_load_request_counter_ += cycles.as_integral();
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
}
WD::WD1770::run_for(cycles);

View File

@ -58,7 +58,7 @@ class Microdisc: public WD::WD1770 {
size_t selected_drive_;
bool irq_enable_ = false;
int paging_flags_ = BASICDisable;
int head_load_request_counter_ = -1;
Cycles::IntType head_load_request_counter_ = -1;
bool head_load_request_ = false;
Delegate *delegate_ = nullptr;
uint8_t last_control_ = 0;

View File

@ -94,7 +94,7 @@ void VideoOutput::run_for(const Cycles cycles) {
#define clamp(action) \
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
int number_of_cycles = cycles.as_int();
int number_of_cycles = int(cycles.as_integral());
while(number_of_cycles) {
int h_counter = counter_ & 63;
int cycles_run_for = 0;

View File

@ -11,7 +11,8 @@
#include "../AmstradCPC/AmstradCPC.hpp"
#include "../Apple/AppleII/AppleII.hpp"
#include "../Apple/Macintosh/Macintosh.hpp"
#include "../Atari2600/Atari2600.hpp"
#include "../Atari/2600/Atari2600.hpp"
#include "../Atari/ST/AtariST.hpp"
#include "../ColecoVision/ColecoVision.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
@ -37,6 +38,7 @@ namespace {
BindD(Apple::II, AppleII)
BindD(Apple::Macintosh, Macintosh)
Bind(Atari2600)
BindD(Atari::ST, AtariST)
BindD(Coleco::Vision, ColecoVision)
BindD(Commodore::Vic20, Vic20)
Bind(Electron)
@ -103,6 +105,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
case Analyser::Machine::AppleII: return "AppleII";
case Analyser::Machine::Atari2600: return "Atari2600";
case Analyser::Machine::AtariST: return "AtariST";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::Macintosh: return "Macintosh";
@ -121,6 +124,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
case Analyser::Machine::AppleII: return "Apple II";
case Analyser::Machine::Atari2600: return "Atari 2600";
case Analyser::Machine::AtariST: return "Atari ST";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::Macintosh: return "Apple Macintosh";

View File

@ -13,3 +13,8 @@ void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *targe
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);
}
}
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target) {
target.resize(source.size() >> 1);
PackBigEndian16(source, target.data());
}

View File

@ -20,5 +20,12 @@ namespace Memory {
*/
void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target);
/*!
Copies the bytes from @c source into @c target, interpreting them
as big-endian 16-bit data. @c target will be resized to the proper size
exactly to contain the contents of @c source.
*/
void PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target);
}
#endif /* MemoryPacker_hpp */

View File

@ -42,7 +42,7 @@ void Video::flush() {
void Video::flush(bool next_sync) {
if(sync_) {
// If in sync, that takes priority. Output the proper amount of sync.
crt_.output_sync(time_since_update_.as_int());
crt_.output_sync(int(time_since_update_.as_integral()));
} else {
// If not presently in sync, then...
@ -50,8 +50,8 @@ void Video::flush(bool next_sync) {
// If there is output data queued, output it either if it's being interrupted by
// sync, or if we're past its end anyway. Otherwise let it be.
int data_length = static_cast<int>(line_data_pointer_ - line_data_);
if(data_length < time_since_update_.as_int() || next_sync) {
auto output_length = std::min(data_length, time_since_update_.as_int());
if(data_length < int(time_since_update_.as_integral()) || next_sync) {
auto output_length = std::min(data_length, int(time_since_update_.as_integral()));
crt_.output_data(output_length);
line_data_pointer_ = line_data_ = nullptr;
time_since_update_ -= HalfCycles(output_length);
@ -61,7 +61,7 @@ void Video::flush(bool next_sync) {
// Any pending pixels being dealt with, pad with the white level.
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = 0xff;
crt_.output_level(time_since_update_.as_int());
crt_.output_level(int(time_since_update_.as_integral()));
}
time_since_update_ = 0;

View File

@ -68,9 +68,6 @@
4B055AC21FAE9AE30060FFFF /* KeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */; };
4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; };
4B055AC51FAE9AEE0060FFFF /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* TIASound.cpp */; };
4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; };
4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; };
@ -110,6 +107,22 @@
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 */; };
4B0ACC02237756ED008902D0 /* Line.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC00237756EC008902D0 /* Line.cpp */; };
4B0ACC03237756F6008902D0 /* Line.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC00237756EC008902D0 /* Line.cpp */; };
4B0ACC2623775819008902D0 /* AtariST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0623775819008902D0 /* AtariST.cpp */; };
4B0ACC2723775819008902D0 /* AtariST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0623775819008902D0 /* AtariST.cpp */; };
4B0ACC2823775819008902D0 /* DMAController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0723775819008902D0 /* DMAController.cpp */; };
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0723775819008902D0 /* DMAController.cpp */; };
4B0ACC2A23775819008902D0 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0823775819008902D0 /* Video.cpp */; };
4B0ACC2B23775819008902D0 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0823775819008902D0 /* Video.cpp */; };
4B0ACC2C23775819008902D0 /* IntelligentKeyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0923775819008902D0 /* IntelligentKeyboard.cpp */; };
4B0ACC2D23775819008902D0 /* IntelligentKeyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0923775819008902D0 /* IntelligentKeyboard.cpp */; };
4B0ACC2E23775819008902D0 /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC1D23775819008902D0 /* TIA.cpp */; };
4B0ACC2F23775819008902D0 /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC1D23775819008902D0 /* TIA.cpp */; };
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC2123775819008902D0 /* TIASound.cpp */; };
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC2123775819008902D0 /* TIASound.cpp */; };
4B0ACC3223775819008902D0 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC2223775819008902D0 /* Atari2600.cpp */; };
4B0ACC3323775819008902D0 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC2223775819008902D0 /* Atari2600.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 */; };
@ -149,7 +162,6 @@
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
4B302184208A550100773308 /* DiskII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B302183208A550100773308 /* DiskII.cpp */; };
4B302185208A550100773308 /* DiskII.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B302183208A550100773308 /* DiskII.cpp */; };
@ -280,8 +292,6 @@
4B89451F201967B4007DE474 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F0201967B4007DE474 /* Tape.cpp */; };
4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F2201967B4007DE474 /* StaticAnalyser.cpp */; };
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F2201967B4007DE474 /* StaticAnalyser.cpp */; };
4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */; };
4B894523201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */; };
4B894524201967B4007DE474 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F9201967B4007DE474 /* Tape.cpp */; };
4B894525201967B4007DE474 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F9201967B4007DE474 /* Tape.cpp */; };
4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944FA201967B4007DE474 /* StaticAnalyser.cpp */; };
@ -319,6 +329,8 @@
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 */; };
4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B92E268234AE35000CD6D1B /* MFP68901.cpp */; };
4B92E26B234AE35100CD6D1B /* MFP68901.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B92E268234AE35000CD6D1B /* MFP68901.cpp */; };
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
4B9378E422A199C600973513 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; };
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; };
@ -616,6 +628,8 @@
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 */; };
4BB307BB235001C300457D33 /* 6850.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB307BA235001C300457D33 /* 6850.cpp */; };
4BB307BC235001C300457D33 /* 6850.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB307BA235001C300457D33 /* 6850.cpp */; };
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 */; };
@ -636,6 +650,12 @@
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 */; };
4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; };
4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; };
4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.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 */; };
@ -688,12 +708,10 @@
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; };
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 */; };
4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* TIASound.cpp */; };
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
@ -771,6 +789,38 @@
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; };
4B0ACC00237756EC008902D0 /* Line.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Line.cpp; sourceTree = "<group>"; };
4B0ACC01237756EC008902D0 /* Line.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Line.hpp; sourceTree = "<group>"; };
4B0ACC0623775819008902D0 /* AtariST.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AtariST.cpp; sourceTree = "<group>"; };
4B0ACC0723775819008902D0 /* DMAController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DMAController.cpp; sourceTree = "<group>"; };
4B0ACC0823775819008902D0 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
4B0ACC0923775819008902D0 /* IntelligentKeyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntelligentKeyboard.cpp; sourceTree = "<group>"; };
4B0ACC0A23775819008902D0 /* AtariST.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AtariST.hpp; sourceTree = "<group>"; };
4B0ACC0B23775819008902D0 /* DMAController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DMAController.hpp; sourceTree = "<group>"; };
4B0ACC0C23775819008902D0 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
4B0ACC0D23775819008902D0 /* IntelligentKeyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntelligentKeyboard.hpp; sourceTree = "<group>"; };
4B0ACC1023775819008902D0 /* Atari16k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari16k.hpp; sourceTree = "<group>"; };
4B0ACC1123775819008902D0 /* Tigervision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tigervision.hpp; sourceTree = "<group>"; };
4B0ACC1223775819008902D0 /* MNetwork.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MNetwork.hpp; sourceTree = "<group>"; };
4B0ACC1323775819008902D0 /* Unpaged.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Unpaged.hpp; sourceTree = "<group>"; };
4B0ACC1423775819008902D0 /* Pitfall2.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Pitfall2.hpp; sourceTree = "<group>"; };
4B0ACC1523775819008902D0 /* ActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ActivisionStack.hpp; sourceTree = "<group>"; };
4B0ACC1623775819008902D0 /* ParkerBros.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ParkerBros.hpp; sourceTree = "<group>"; };
4B0ACC1723775819008902D0 /* Atari32k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari32k.hpp; sourceTree = "<group>"; };
4B0ACC1823775819008902D0 /* MegaBoy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MegaBoy.hpp; sourceTree = "<group>"; };
4B0ACC1923775819008902D0 /* CBSRAMPlus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CBSRAMPlus.hpp; sourceTree = "<group>"; };
4B0ACC1A23775819008902D0 /* Atari8k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari8k.hpp; sourceTree = "<group>"; };
4B0ACC1B23775819008902D0 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B0ACC1C23775819008902D0 /* CommaVid.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommaVid.hpp; sourceTree = "<group>"; };
4B0ACC1D23775819008902D0 /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = "<group>"; };
4B0ACC1E23775819008902D0 /* TIASound.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIASound.hpp; sourceTree = "<group>"; };
4B0ACC1F23775819008902D0 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
4B0ACC2023775819008902D0 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
4B0ACC2123775819008902D0 /* TIASound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIASound.cpp; sourceTree = "<group>"; };
4B0ACC2223775819008902D0 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
4B0ACC2323775819008902D0 /* Bus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
4B0ACC2423775819008902D0 /* PIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
4B0ACC2523775819008902D0 /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.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>"; };
@ -835,9 +885,6 @@
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; };
4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = "<group>"; };
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
4B302182208A550100773308 /* DiskII.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskII.hpp; sourceTree = "<group>"; };
@ -1051,8 +1098,6 @@
4B8944F0201967B4007DE474 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
4B8944F1201967B4007DE474 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = "<group>"; };
4B8944F2201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B8944F8201967B4007DE474 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
4B8944F9201967B4007DE474 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
@ -1102,6 +1147,8 @@
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>"; };
4B92E268234AE35000CD6D1B /* MFP68901.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFP68901.cpp; sourceTree = "<group>"; };
4B92E269234AE35000CD6D1B /* MFP68901.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFP68901.hpp; 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>"; };
@ -1405,6 +1452,8 @@
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>"; };
4BB307B9235001C300457D33 /* 6850.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6850.hpp; sourceTree = "<group>"; };
4BB307BA235001C300457D33 /* 6850.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6850.cpp; 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>"; };
@ -1443,6 +1492,14 @@
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>"; };
4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BC1316E2346DE5000E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4BC131732346DE9100E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BC131742346DE9100E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4BC131782346DF2B00E4FF3D /* MSA.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSA.cpp; sourceTree = "<group>"; };
4BC131792346DF2B00E4FF3D /* MSA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSA.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>"; };
@ -1521,10 +1578,7 @@
4BE32314205328FF006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
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>"; };
@ -1533,23 +1587,6 @@
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SoundGenerator.cpp; path = Electron/SoundGenerator.cpp; sourceTree = "<group>"; };
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SoundGenerator.hpp; path = Electron/SoundGenerator.hpp; sourceTree = "<group>"; };
4BEA52641DF3472B007E74F2 /* TIASound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIASound.cpp; sourceTree = "<group>"; };
4BEA52651DF3472B007E74F2 /* TIASound.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIASound.hpp; sourceTree = "<group>"; };
4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4BEAC0821E7E0DF800EE56B2 /* ActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ActivisionStack.hpp; sourceTree = "<group>"; };
4BEAC0831E7E0DF800EE56B2 /* Atari16k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari16k.hpp; sourceTree = "<group>"; };
4BEAC0841E7E0DF800EE56B2 /* Atari32k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari32k.hpp; sourceTree = "<group>"; };
4BEAC0851E7E0DF800EE56B2 /* Atari8k.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari8k.hpp; sourceTree = "<group>"; };
4BEAC0861E7E0DF800EE56B2 /* CBSRAMPlus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CBSRAMPlus.hpp; sourceTree = "<group>"; };
4BEAC0871E7E0DF800EE56B2 /* CommaVid.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommaVid.hpp; sourceTree = "<group>"; };
4BEAC0881E7E0DF800EE56B2 /* MegaBoy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MegaBoy.hpp; sourceTree = "<group>"; };
4BEAC0891E7E0DF800EE56B2 /* MNetwork.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MNetwork.hpp; sourceTree = "<group>"; };
4BEAC08A1E7E0DF800EE56B2 /* ParkerBros.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ParkerBros.hpp; sourceTree = "<group>"; };
4BEAC08B1E7E0DF800EE56B2 /* Tigervision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tigervision.hpp; sourceTree = "<group>"; };
4BEAC08C1E7E0DF800EE56B2 /* Unpaged.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Unpaged.hpp; sourceTree = "<group>"; };
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
4BEAC08E1E7E110500EE56B2 /* Pitfall2.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Pitfall2.hpp; sourceTree = "<group>"; };
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSXDSK.cpp; sourceTree = "<group>"; };
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = "<group>"; };
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DiskROM.cpp; path = MSX/DiskROM.cpp; sourceTree = "<group>"; };
@ -1646,6 +1683,76 @@
path = ../SDL;
sourceTree = "<group>";
};
4B0ACBFF237756EC008902D0 /* Serial */ = {
isa = PBXGroup;
children = (
4B0ACC00237756EC008902D0 /* Line.cpp */,
4B0ACC01237756EC008902D0 /* Line.hpp */,
);
path = Serial;
sourceTree = "<group>";
};
4B0ACC0423775819008902D0 /* Atari */ = {
isa = PBXGroup;
children = (
4B0ACC0523775819008902D0 /* ST */,
4B0ACC0E23775819008902D0 /* 2600 */,
);
path = Atari;
sourceTree = "<group>";
};
4B0ACC0523775819008902D0 /* ST */ = {
isa = PBXGroup;
children = (
4B0ACC0623775819008902D0 /* AtariST.cpp */,
4B0ACC0723775819008902D0 /* DMAController.cpp */,
4B0ACC0823775819008902D0 /* Video.cpp */,
4B0ACC0923775819008902D0 /* IntelligentKeyboard.cpp */,
4B0ACC0A23775819008902D0 /* AtariST.hpp */,
4B0ACC0B23775819008902D0 /* DMAController.hpp */,
4B0ACC0C23775819008902D0 /* Video.hpp */,
4B0ACC0D23775819008902D0 /* IntelligentKeyboard.hpp */,
);
path = ST;
sourceTree = "<group>";
};
4B0ACC0E23775819008902D0 /* 2600 */ = {
isa = PBXGroup;
children = (
4B0ACC0F23775819008902D0 /* Cartridges */,
4B0ACC1D23775819008902D0 /* TIA.cpp */,
4B0ACC1E23775819008902D0 /* TIASound.hpp */,
4B0ACC1F23775819008902D0 /* Atari2600Inputs.h */,
4B0ACC2023775819008902D0 /* Atari2600.hpp */,
4B0ACC2123775819008902D0 /* TIASound.cpp */,
4B0ACC2223775819008902D0 /* Atari2600.cpp */,
4B0ACC2323775819008902D0 /* Bus.hpp */,
4B0ACC2423775819008902D0 /* PIA.hpp */,
4B0ACC2523775819008902D0 /* TIA.hpp */,
);
path = 2600;
sourceTree = "<group>";
};
4B0ACC0F23775819008902D0 /* Cartridges */ = {
isa = PBXGroup;
children = (
4B0ACC1023775819008902D0 /* Atari16k.hpp */,
4B0ACC1123775819008902D0 /* Tigervision.hpp */,
4B0ACC1223775819008902D0 /* MNetwork.hpp */,
4B0ACC1323775819008902D0 /* Unpaged.hpp */,
4B0ACC1423775819008902D0 /* Pitfall2.hpp */,
4B0ACC1523775819008902D0 /* ActivisionStack.hpp */,
4B0ACC1623775819008902D0 /* ParkerBros.hpp */,
4B0ACC1723775819008902D0 /* Atari32k.hpp */,
4B0ACC1823775819008902D0 /* MegaBoy.hpp */,
4B0ACC1923775819008902D0 /* CBSRAMPlus.hpp */,
4B0ACC1A23775819008902D0 /* Atari8k.hpp */,
4B0ACC1B23775819008902D0 /* Cartridge.hpp */,
4B0ACC1C23775819008902D0 /* CommaVid.hpp */,
);
path = Cartridges;
sourceTree = "<group>";
};
4B0CCC411C62D0B3001CAC5F /* CRT */ = {
isa = PBXGroup;
children = (
@ -1820,23 +1927,6 @@
path = Utility;
sourceTree = "<group>";
};
4B2E2D961C3A06EC00138695 /* Atari2600 */ = {
isa = PBXGroup;
children = (
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
4BEA52641DF3472B007E74F2 /* TIASound.cpp */,
4BE7C9161E3D397100A5496D /* TIA.cpp */,
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */,
4BEA52671DF34909007E74F2 /* PIA.hpp */,
4BEA52651DF3472B007E74F2 /* TIASound.hpp */,
4BE7C9171E3D397100A5496D /* TIA.hpp */,
4BEAC0801E7E0DF800EE56B2 /* Cartridges */,
);
path = Atari2600;
sourceTree = "<group>";
};
4B2E2D9E1C3A070900138695 /* Electron */ = {
isa = PBXGroup;
children = (
@ -2037,11 +2127,12 @@
4B0333AD2094081A0050B93D /* AppleDSK.cpp */,
4B45188F1F75FD1B00926311 /* CPCDSK.cpp */,
4B4518911F75FD1B00926311 /* D64.cpp */,
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */,
4BAF2B4C2004580C00480230 /* DMK.cpp */,
4B4518931F75FD1B00926311 /* G64.cpp */,
4B4518951F75FD1B00926311 /* HFE.cpp */,
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */,
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
4BC131782346DF2B00E4FF3D /* MSA.cpp */,
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */,
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */,
4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */,
@ -2051,11 +2142,12 @@
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
4B4518901F75FD1B00926311 /* CPCDSK.hpp */,
4B4518921F75FD1B00926311 /* D64.hpp */,
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */,
4BAF2B4D2004580C00480230 /* DMK.hpp */,
4B4518941F75FD1B00926311 /* G64.hpp */,
4B4518961F75FD1B00926311 /* HFE.hpp */,
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */,
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
4BC131792346DF2B00E4FF3D /* MSA.hpp */,
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */,
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */,
4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */,
@ -2487,7 +2579,8 @@
4B8944EB201967B4007DE474 /* Acorn */,
4B894514201967B4007DE474 /* AmstradCPC */,
4B15A9FE20824C9F005E6C8D /* AppleII */,
4B8944F3201967B4007DE474 /* Atari */,
4BC1316C2346DE5000E4FF3D /* Atari2600 */,
4BC131722346DE9100E4FF3D /* AtariST */,
4B7A90EA20410A85008514A2 /* Coleco */,
4B8944FB201967B4007DE474 /* Commodore */,
4B894507201967B4007DE474 /* Disassembler */,
@ -2516,16 +2609,6 @@
path = Acorn;
sourceTree = "<group>";
};
4B8944F3201967B4007DE474 /* Atari */ = {
isa = PBXGroup;
children = (
4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */,
4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */,
4BE3231720532CC0006EF799 /* Target.hpp */,
);
path = Atari;
sourceTree = "<group>";
};
4B8944F6201967B4007DE474 /* Oric */ = {
isa = PBXGroup;
children = (
@ -2619,6 +2702,15 @@
path = QuadratureMouse;
sourceTree = "<group>";
};
4B92E267234AE35000CD6D1B /* 68901 */ = {
isa = PBXGroup;
children = (
4B92E268234AE35000CD6D1B /* MFP68901.cpp */,
4B92E269234AE35000CD6D1B /* MFP68901.hpp */,
);
path = 68901;
sourceTree = "<group>";
};
4B9F11C72272375400701480 /* QL Startup */ = {
isa = PBXGroup;
children = (
@ -2933,6 +3025,15 @@
path = "Wolfgang Lorenz 6502 test suite";
sourceTree = "<group>";
};
4BB307B8235001C300457D33 /* 6850 */ = {
isa = PBXGroup;
children = (
4BB307B9235001C300457D33 /* 6850.hpp */,
4BB307BA235001C300457D33 /* 6850.cpp */,
);
path = 6850;
sourceTree = "<group>";
};
4BB4BFB622A4372E0069048D /* Macintosh */ = {
isa = PBXGroup;
children = (
@ -3097,7 +3198,7 @@
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4BCE0048227CE8CA000CA200 /* Apple */,
4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B0ACC0423775819008902D0 /* Atari */,
4B7A90E22041097C008514A2 /* ColecoVision */,
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
4B2E2D9E1C3A070900138695 /* Electron */,
@ -3179,23 +3280,46 @@
path = "Joystick Manager";
sourceTree = "<group>";
};
4BC1316C2346DE5000E4FF3D /* Atari2600 */ = {
isa = PBXGroup;
children = (
4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */,
4BC1316E2346DE5000E4FF3D /* Target.hpp */,
4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */,
);
path = Atari2600;
sourceTree = "<group>";
};
4BC131722346DE9100E4FF3D /* AtariST */ = {
isa = PBXGroup;
children = (
4BC131732346DE9100E4FF3D /* StaticAnalyser.hpp */,
4BC131742346DE9100E4FF3D /* Target.hpp */,
4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */,
);
path = AtariST;
sourceTree = "<group>";
};
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
4BDACBE922FFA5B50045EF7E /* 5380 */,
4BD468F81D8DF4290084958B /* 1770 */,
4BDACBE922FFA5B50045EF7E /* 5380 */,
4BC9DF4B1D04691600F44158 /* 6522 */,
4B1E85791D174DEC001EF87D /* 6532 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
4BE845221F2FF7F400A5EA22 /* 6845 */,
4BB307B8235001C300457D33 /* 6850 */,
4BD9137C1F3115AC009BCF85 /* 8255 */,
4BBC951F1F368D87008F4C34 /* 8272 */,
4BB244D222AABAF500BE20E5 /* 8530 */,
4B0E04F71FC9F2C800F43484 /* 9918 */,
4B92E267234AE35000CD6D1B /* 68901 */,
4B595FAA2086DFBA0083CAA8 /* AudioToggle */,
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
4B302181208A550100773308 /* DiskII */,
4B4B1A39200198C900A0F866 /* KonamiSCC */,
4B0ACBFF237756EC008902D0 /* Serial */,
4BB0A6582044FD3000FB3688 /* SN76489 */,
);
name = Components;
@ -3424,26 +3548,6 @@
name = Zexall;
sourceTree = "<group>";
};
4BEAC0801E7E0DF800EE56B2 /* Cartridges */ = {
isa = PBXGroup;
children = (
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */,
4BEAC0821E7E0DF800EE56B2 /* ActivisionStack.hpp */,
4BEAC0851E7E0DF800EE56B2 /* Atari8k.hpp */,
4BEAC0831E7E0DF800EE56B2 /* Atari16k.hpp */,
4BEAC0841E7E0DF800EE56B2 /* Atari32k.hpp */,
4BEAC0861E7E0DF800EE56B2 /* CBSRAMPlus.hpp */,
4BEAC0871E7E0DF800EE56B2 /* CommaVid.hpp */,
4BEAC0881E7E0DF800EE56B2 /* MegaBoy.hpp */,
4BEAC0891E7E0DF800EE56B2 /* MNetwork.hpp */,
4BEAC08A1E7E0DF800EE56B2 /* ParkerBros.hpp */,
4BEAC08E1E7E110500EE56B2 /* Pitfall2.hpp */,
4BEAC08B1E7E0DF800EE56B2 /* Tigervision.hpp */,
4BEAC08C1E7E0DF800EE56B2 /* Unpaged.hpp */,
);
path = Cartridges;
sourceTree = "<group>";
};
4BEE0A691D72496600532C7B /* Cartridge */ = {
isa = PBXGroup;
children = (
@ -3970,6 +4074,7 @@
4B1B88C9202E469400B67DFF /* MultiJoystickMachine.cpp in Sources */,
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */,
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
4B8318B122D3E53A006DB630 /* DiskIICard.cpp in Sources */,
@ -3992,7 +4097,9 @@
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B0ACC2D23775819008902D0 /* IntelligentKeyboard.cpp in Sources */,
4B894539201967B4007DE474 /* Tape.cpp in Sources */,
4B92E26B234AE35100CD6D1B /* MFP68901.cpp in Sources */,
4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */,
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */,
@ -4004,6 +4111,7 @@
4BD67DCC209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */,
4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */,
4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */,
4B0ACC2B23775819008902D0 /* Video.cpp in Sources */,
4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */,
4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */,
4BCD634A22D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
@ -4012,7 +4120,6 @@
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 */,
@ -4030,9 +4137,11 @@
4B055A8E1FAE85920060FFFF /* BestEffortUpdater.cpp in Sources */,
4B055AB01FAE86070060FFFF /* PulseQueuedTape.cpp in Sources */,
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */,
4BB307BC235001C300457D33 /* 6850.cpp in Sources */,
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
4B89451D201967B4007DE474 /* Disk.cpp in Sources */,
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */,
4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */,
4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
@ -4046,16 +4155,18 @@
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 */,
4B0ACC2723775819008902D0 /* AtariST.cpp in Sources */,
4B8318B922D3E56D006DB630 /* MemoryPacker.cpp in Sources */,
4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */,
4B0ACC2F23775819008902D0 /* TIA.cpp in Sources */,
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */,
4B055AE91FAE9B990060FFFF /* 6502Base.cpp in Sources */,
4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */,
4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */,
4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */,
4B8318B222D3E53C006DB630 /* Video.cpp in Sources */,
4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */,
@ -4064,7 +4175,9 @@
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */,
4B0ACC03237756F6008902D0 /* Line.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */,
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
@ -4073,6 +4186,7 @@
4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */,
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */,
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */,
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */,
4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */,
@ -4085,7 +4199,6 @@
4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */,
4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */,
4B89451F201967B4007DE474 /* Tape.cpp in Sources */,
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
4B8318B422D3E546006DB630 /* DriveSpeedAccumulator.cpp in Sources */,
@ -4097,6 +4210,7 @@
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4B0ACC3323775819008902D0 /* Atari2600.cpp in Sources */,
4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */,
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
@ -4118,7 +4232,6 @@
4B055A9F1FAE85DA0060FFFF /* HFE.cpp in Sources */,
4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */,
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */,
4B894523201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */,
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
@ -4150,6 +4263,7 @@
4B89451E201967B4007DE474 /* Tape.cpp in Sources */,
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */,
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
@ -4161,6 +4275,7 @@
4BD424DF2193B5340097291A /* TextureTarget.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */,
4B0ACC2623775819008902D0 /* AtariST.cpp in Sources */,
4B894530201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */,
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
@ -4184,6 +4299,9 @@
4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */,
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */,
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
4B0ACC2C23775819008902D0 /* IntelligentKeyboard.cpp in Sources */,
4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */,
4B0ACC2A23775819008902D0 /* Video.cpp in Sources */,
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
@ -4211,6 +4329,7 @@
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */,
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
4B0ACC2E23775819008902D0 /* TIA.cpp in Sources */,
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
@ -4218,6 +4337,7 @@
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */,
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,
4B0ACC3223775819008902D0 /* Atari2600.cpp in Sources */,
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
@ -4259,9 +4379,7 @@
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
@ -4272,6 +4390,7 @@
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B0ACC02237756ED008902D0 /* Line.cpp in Sources */,
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
@ -4286,7 +4405,6 @@
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */,
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */,
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */,
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
@ -4294,7 +4412,7 @@
4BCE0051227CE8CA000CA200 /* Video.cpp in Sources */,
4B894536201967B4007DE474 /* Z80.cpp in Sources */,
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */,
4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */,
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
@ -4312,13 +4430,17 @@
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */,
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */,
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4BB307BB235001C300457D33 /* 6850.cpp in Sources */,
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */,
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */,
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */,
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B0ACC2823775819008902D0 /* DMAController.cpp in Sources */,
4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */,
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */,
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */,

View File

@ -386,6 +386,30 @@
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>msa</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Atari ST 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>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>

View File

@ -79,7 +79,7 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
}
- (uint32_t)timestamp {
return _processor->get_timestamp().as_int();
return uint32_t(_processor->get_timestamp().as_integral());
}
- (void)setIrqLine:(BOOL)irqLine {

View File

@ -155,7 +155,7 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
}
- (int)completedHalfCycles {
return _processor->get_timestamp().as_int();
return int(_processor->get_timestamp().as_integral());
}
- (void)setNmiLine:(BOOL)nmiLine {
@ -216,7 +216,7 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
}
capture.address = address;
capture.value = value;
capture.timeStamp = timeStamp.as_int();
capture.timeStamp = int(timeStamp.as_integral());
[_busOperationCaptures addObject:capture];
}

View File

@ -31,14 +31,14 @@
int c = 5;
bool vsync = _video->vsync();
while(c--) {
int remaining_time_in_state = _video->get_next_sequence_point().as_int();
auto remaining_time_in_state = _video->get_next_sequence_point().as_integral();
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());
XCTAssertEqual(remaining_time_in_state, _video->get_next_sequence_point().as_integral());
}
vsync ^= true;
}

View File

@ -55,7 +55,7 @@
vdp.set_register(1, 0x8a);
// Get time until interrupt.
int time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1;
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
// Check that an interrupt is now scheduled.
NSAssert(time_until_interrupt != -2, @"No interrupt scheduled");
@ -74,7 +74,7 @@
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
// Check interrupt flag isn't set prior to the reported time.
time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1;
time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
vdp.run_for(HalfCycles(time_until_interrupt));
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
@ -101,7 +101,7 @@
// Clear the pending interrupt and ask about the next one (i.e. the first one).
vdp.get_register(1);
int time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1;
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
// Check that an interrupt is now scheduled.
NSAssert(time_until_interrupt != -2, @"No interrupt scheduled");
@ -135,7 +135,7 @@
// Now run through an entire frame...
int half_cycles = 262*228*2;
int last_time_until_interrupt = vdp.get_time_until_interrupt().as_int();
auto last_time_until_interrupt = vdp.get_time_until_interrupt().as_integral();
while(half_cycles--) {
// Validate that an interrupt happened if one was expected, and clear anything that's present.
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == 0), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
@ -144,7 +144,7 @@
vdp.run_for(HalfCycles(1));
// Get the time until interrupt.
int time_until_interrupt = vdp.get_time_until_interrupt().as_int();
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral();
NSAssert(time_until_interrupt != -1, @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
@ -160,11 +160,11 @@
- (void)testTimeUntilLine {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP);
int time_until_line = vdp.get_time_until_line(-1).as_int();
auto time_until_line = vdp.get_time_until_line(-1).as_integral();
for(int c = 0; c < 262*228*5; ++c) {
vdp.run_for(HalfCycles(1));
const int time_remaining_until_line = vdp.get_time_until_line(-1).as_int();
const auto time_remaining_until_line = vdp.get_time_until_line(-1).as_integral();
--time_until_line;
if(time_until_line) {
NSAssert(time_remaining_until_line == time_until_line, @"Discontinuity found in distance-to-line prediction; expected %d but got %d", time_until_line, time_remaining_until_line);

View File

@ -128,7 +128,7 @@ class RAM68000: public CPU::MC68000::BusHandler {
}
int get_cycle_count() {
return duration_.as_int() >> 1;
return int(duration_.as_integral()) >> 1;
}
private:

View File

@ -24,7 +24,8 @@ SOURCES += glob.glob('../../Analyser/Static/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Acorn/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AmstradCPC/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AppleII/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Atari/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Atari2600/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AtariST/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
@ -39,6 +40,8 @@ SOURCES += glob.glob('../../Components/1770/*.cpp')
SOURCES += glob.glob('../../Components/5380/*.cpp')
SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp')
SOURCES += glob.glob('../../Components/6560/*.cpp')
SOURCES += glob.glob('../../Components/6850/*.cpp')
SOURCES += glob.glob('../../Components/68901/*.cpp')
SOURCES += glob.glob('../../Components/8272/*.cpp')
SOURCES += glob.glob('../../Components/8530/*.cpp')
SOURCES += glob.glob('../../Components/9918/*.cpp')
@ -48,6 +51,7 @@ SOURCES += glob.glob('../../Components/AY38910/*.cpp')
SOURCES += glob.glob('../../Components/DiskII/*.cpp')
SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
SOURCES += glob.glob('../../Components/Serial/*.cpp')
SOURCES += glob.glob('../../Concurrency/*.cpp')
@ -59,7 +63,8 @@ SOURCES += glob.glob('../../Machines/*.cpp')
SOURCES += glob.glob('../../Machines/AmstradCPC/*.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/Atari/2600/*.cpp')
SOURCES += glob.glob('../../Machines/Atari/ST/*.cpp')
SOURCES += glob.glob('../../Machines/ColecoVision/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp')

View File

@ -272,7 +272,7 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_
colour_burst_amplitude_);
}
// if this is vertical retrace then adcance a field
// if this is vertical retrace then advance a field
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) {
if(delegate_) {
frames_since_last_delegate_call_++;

View File

@ -26,6 +26,16 @@
#define PADHEX(n) std::hex << std::setfill('0') << std::setw(n)
#define PADDEC(n) std::dec << std::setfill('0') << std::setw(n)
#ifdef LOG_PREFIX
#define LOG(x) std::cout << LOG_PREFIX << x << std::endl
#define LOGNBR(x) std::cout << LOG_PREFIX << x
#define ERROR(x) std::cerr << LOG_PREFIX << x << std::endl
#define ERRORNBR(x) std::cerr << LOG_PREFIX << x
#else
#define LOG(x) std::cout << x << std::endl
#define LOGNBR(x) std::cout << x
@ -34,5 +44,7 @@
#endif
#endif
#endif /* Log_h */

View File

@ -228,11 +228,8 @@ void ScanTarget::end_data(size_t actual_length) {
&write_area_texture_[size_t(write_pointers_.write_area) * data_type_size_],
data_type_size_);
// The write area was allocated in the knowledge that there's sufficient
// distance left on the current line, but there's a risk of exactly filling
// the final line, in which case this should wrap back to 0.
// Advance to the end of the current run.
write_pointers_.write_area += actual_length + 1;
write_pointers_.write_area %= write_area_texture_.size();
// Also bookend the end.
memcpy(
@ -240,6 +237,11 @@ void ScanTarget::end_data(size_t actual_length) {
&write_area_texture_[size_t(write_pointers_.write_area - 2) * data_type_size_],
data_type_size_);
// The write area was allocated in the knowledge that there's sufficient
// distance left on the current line, but there's a risk of exactly filling
// the final line, in which case this should wrap back to 0.
write_pointers_.write_area %= (write_area_texture_.size() / data_type_size_);
// Record that no further end_data calls are expected.
data_is_allocated_ = false;
}
@ -353,7 +355,7 @@ void ScanTarget::setup_pipeline() {
// TODO: flush output.
data_type_size_ = data_type_size;
write_area_texture_.resize(2048*2048*data_type_size_);
write_area_texture_.resize(WriteAreaWidth*WriteAreaHeight*data_type_size_);
write_pointers_.scan_buffer = 0;
write_pointers_.write_area = 0;

View File

@ -106,7 +106,7 @@ template <typename T> class LowpassSpeaker: public Speaker {
void run_for(const Cycles cycles) {
if(!delegate_) return;
std::size_t cycles_remaining = size_t(cycles.as_int());
std::size_t cycles_remaining = size_t(cycles.as_integral());
if(!cycles_remaining) return;
FilterParameters filter_parameters;

View File

@ -84,6 +84,9 @@ struct Microcycle {
/// is synchronised with the E clock to satisfy a valid peripheral address request.
static const int IsPeripheral = 1 << 9;
/// Provides the 68000's bus grant line — indicating whether a bus request has been acknowledged.
static const int BusGrant = 1 << 10;
/// Contains a valid combination of the various static const int flags, describing the operation
/// performed by this Microcycle.
int operation = 0;
@ -193,6 +196,75 @@ struct Microcycle {
return (address ? (*address) & 0x00fffffe : 0) >> 1;
}
/*!
@returns the value on the data bus all 16 bits, with any inactive lines
(as er the upper and lower data selects) being represented by 1s. Assumes
this is a write cycle.
*/
forceinline uint16_t value16() const {
if(operation & SelectWord) return value->full;
const auto shift = byte_shift();
return uint16_t((value->halves.low << shift) | (0xff00 >> shift));
}
/*!
@returns the value currently on the high 8 lines of the data bus if any;
@c 0xff otherwise. Assumes this is a write cycle.
*/
forceinline uint8_t value8_high() const {
if(operation & SelectWord) {
return uint8_t(value->full >> 8);
}
return uint8_t(value->halves.low | (0xff00 >> ((*address & 1) << 3)));
}
/*!
@returns the value currently on the low 8 lines of the data bus if any;
@c 0xff otherwise. Assumes this is a write cycle.
*/
forceinline uint8_t value8_low() const {
if(operation & SelectWord) {
return uint8_t(value->full);
}
return uint8_t(value->halves.low | (0x00ff << ((*address & 1) << 3)));
}
/*!
Sets to @c value the 8- or 16-bit portion of the supplied value that is
currently being read. Assumes this is a read cycle.
*/
forceinline void set_value16(uint16_t v) const {
if(operation & Microcycle::SelectWord) {
value->full = v;
} else {
value->halves.low = uint8_t(v >> byte_shift());
}
}
/*!
Equivalent to set_value16((v << 8) | 0x00ff).
*/
forceinline void set_value8_high(uint8_t v) const {
if(operation & Microcycle::SelectWord) {
value->full = uint16_t(0x00ff | (v << 8));
} else {
value->halves.low = uint8_t(v | (0xff00 >> ((*address & 1) << 3)));
}
}
/*!
Equivalent to set_value16((v) | 0xff00).
*/
forceinline void set_value8_low(uint8_t v) const {
if(operation & Microcycle::SelectWord) {
value->full = 0xff00 | v;
} else {
value->halves.low = uint8_t(v | (0x00ff << ((*address & 1) << 3)));
}
}
/*!
@returns the same value as word_address() for any Microcycle with the NewAddress or
SameAddress flags set; undefined behaviour otherwise.
@ -302,13 +374,13 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform = false> cla
}
/// Sets the bus request line.
/// This are of functionality is TODO.
/// This area of functionality is TODO.
inline void set_bus_request(bool bus_request) {
bus_request_ = bus_request;
}
/// Sets the bus acknowledge line.
/// This are of functionality is TODO.
/// This area of functionality is TODO.
inline void set_bus_acknowledge(bool bus_acknowledge) {
bus_acknowledge_ = bus_acknowledge;
}

View File

@ -241,6 +241,9 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
continue;
case ExecutionState::BeginInterrupt:
#ifdef LOG_TRACE
should_log = true;
#endif
active_program_ = nullptr;
active_micro_op_ = interrupt_micro_ops_;
execution_state_ = ExecutionState::Executing;
@ -307,11 +310,11 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
}
#ifdef LOG_TRACE
const uint32_t fetched_pc = (program_counter_.full - 4)&0xffffff;
// const uint32_t fetched_pc = (program_counter_.full - 4)&0xffffff;
// should_log |= fetched_pc == 0x6d9c;
should_log = (fetched_pc >= 0x41806A && fetched_pc <= 0x418618);
// should_log |= fetched_pc == 0x4012A2;
// should_log = (fetched_pc >= 0x41806A && fetched_pc <= 0x418618);
// should_log |= fetched_pc == 0x4012A2;
// should_log &= fetched_pc != 0x4012AE;
// should_log = (fetched_pc >= 0x408D66) && (fetched_pc <= 0x408D84);
@ -1997,6 +2000,8 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
// Otherwise, the vector is whatever we were just told it is.
effective_address_[0].full = uint32_t(source_bus_data_[0].halves.low.halves.low << 2);
// printf("Interrupt vector: %06x\n", effective_address_[0].full);
break;
case int_type(MicroOp::Action::CopyNextWord):

View File

@ -1056,7 +1056,7 @@ struct ProcessorStorageConstructor {
break;
case bw(PreDec): // [AND/OR/EOR].bw Dn, -(An)
op(dec(ea_register) | MicroOp::SourceMask, seq("n nrd", { a(ea_register) }, !is_byte_access));
op(dec(ea_register) | MicroOp::DestinationMask, seq("n nrd", { a(ea_register) }, !is_byte_access));
op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access));
break;

View File

@ -941,7 +941,7 @@ template < class T,
operation_indices.push_back(target.all_operations.size());
for(std::size_t t = 0; t < lengths[c];) {
// Skip zero-length bus cycles.
if(table[c][t].type == MicroOp::BusOperation && table[c][t].machine_cycle.length.as_int() == 0) {
if(table[c][t].type == MicroOp::BusOperation && table[c][t].machine_cycle.length.as_integral() == 0) {
t++;
continue;
}

View File

@ -1,4 +1,13 @@
THIS EMULATOR DOES NOT EMULATE THE ATARI ST.
# Emulation
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
tos100.img; a 196kb image of the UK Atari ST TOS 1.00 ROM.
# Test Cases.
Included here are Atari ST-targeted versions of EmuTOS, being a significant chunk of freely available and redistributable 68000 code that can be used to test the 68000-in-progress.

View File

@ -13,9 +13,9 @@
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(clock_rate.as_int(), 1, 1)) {
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
empty_drive_(new Drive(int(clock_rate.as_integral()), 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);
@ -48,7 +48,7 @@ void Controller::process_event(const Drive::Event &event) {
}
void Controller::advance(const Cycles cycles) {
if(is_reading_) pll_->run_for(Cycles(cycles.as_int() * clock_rate_multiplier_));
if(is_reading_) pll_->run_for(Cycles(cycles.as_integral() * clock_rate_multiplier_));
}
void Controller::process_write_completed() {
@ -61,7 +61,7 @@ void Controller::set_expected_bit_length(Time bit_length) {
bit_length_ = bit_length;
bit_length_.simplify();
Time cycles_per_bit = Storage::Time(clock_rate_) * bit_length;
Time cycles_per_bit = Storage::Time(int(clock_rate_)) * bit_length;
cycles_per_bit.simplify();
// this conversion doesn't need to be exact because there's a lot of variation to be taken

View File

@ -106,8 +106,8 @@ class Controller:
private:
Time bit_length_;
int clock_rate_multiplier_ = 1;
int clock_rate_ = 1;
Cycles::IntType clock_rate_multiplier_ = 1;
Cycles::IntType clock_rate_ = 1;
bool is_reading_ = true;

View File

@ -18,14 +18,14 @@ DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, std::size_t l
clocks_per_bit_(clocks_per_bit) {}
void DigitalPhaseLockedLoop::run_for(const Cycles cycles) {
offset_ += cycles.as_int();
phase_ += cycles.as_int();
offset_ += cycles.as_integral();
phase_ += cycles.as_integral();
if(phase_ >= window_length_) {
int windows_crossed = phase_ / window_length_;
auto windows_crossed = phase_ / window_length_;
// check whether this triggers any 0s, if anybody cares
if(delegate_) {
if(window_was_filled_) windows_crossed--;
if(window_was_filled_) --windows_crossed;
for(int c = 0; c < windows_crossed; c++)
delegate_->digital_phase_locked_loop_output_bit(0);
}
@ -44,16 +44,16 @@ void DigitalPhaseLockedLoop::add_pulse() {
}
}
void DigitalPhaseLockedLoop::post_phase_offset(int new_phase, int new_offset) {
void DigitalPhaseLockedLoop::post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) {
offset_history_[offset_history_pointer_] = new_offset;
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
// use an unweighted average of the stored offsets to compute current window size,
// bucketing them by rounding to the nearest multiple of the base clocks per bit
int total_spacing = 0;
int total_divisor = 0;
for(int offset : offset_history_) {
int multiple = (offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_;
Cycles::IntType total_spacing = 0;
Cycles::IntType total_divisor = 0;
for(auto offset : offset_history_) {
auto multiple = (offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_;
if(!multiple) continue;
total_divisor += multiple;
total_spacing += offset;
@ -62,7 +62,7 @@ void DigitalPhaseLockedLoop::post_phase_offset(int new_phase, int new_offset) {
window_length_ = total_spacing / total_divisor;
}
int error = new_phase - (window_length_ >> 1);
auto error = new_phase - (window_length_ >> 1);
// use a simple spring mechanism as a lowpass filter for phase
phase_ -= (error + 1) >> 1;

Some files were not shown because too many files have changed in this diff Show More