mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-09 00:37:27 +00:00
Merge pull request #662 from TomHarte/AtariST
Adds basic and very buggy Atari ST emulation.
This commit is contained in:
commit
cef07038c1
@ -15,6 +15,7 @@ enum class Machine {
|
||||
AmstradCPC,
|
||||
AppleII,
|
||||
Atari2600,
|
||||
AtariST,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Macintosh,
|
||||
|
@ -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
|
@ -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);
|
||||
|
@ -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 {
|
26
Analyser/Static/AtariST/StaticAnalyser.cpp
Normal file
26
Analyser/Static/AtariST/StaticAnalyser.cpp
Normal 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;
|
||||
}
|
27
Analyser/Static/AtariST/StaticAnalyser.hpp
Normal file
27
Analyser/Static/AtariST/StaticAnalyser.hpp
Normal 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 */
|
23
Analyser/Static/AtariST/Target.hpp
Normal file
23
Analyser/Static/AtariST/Target.hpp
Normal 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 */
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -9,9 +9,9 @@
|
||||
#ifndef ForceInline_hpp
|
||||
#define ForceInline_hpp
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifndef NDEBUG
|
||||
|
||||
#define forceinline
|
||||
#define forceinline inline
|
||||
|
||||
#else
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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_;
|
||||
|
@ -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
189
Components/6850/6850.cpp
Normal 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
135
Components/6850/6850.hpp
Normal 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 */
|
349
Components/68901/MFP68901.cpp
Normal file
349
Components/68901/MFP68901.cpp
Normal 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 Channel’s 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;
|
||||
}
|
157
Components/68901/MFP68901.hpp
Normal file
157
Components/68901/MFP68901.hpp
Normal 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 */
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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) {}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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
140
Components/Serial/Line.cpp
Normal 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
112
Components/Serial/Line.hpp
Normal 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 */
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
@ -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++) {
|
@ -11,7 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../Components/6532/6532.hpp"
|
||||
#include "../../../Components/6532/6532.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
@ -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) {
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
576
Machines/Atari/ST/AtariST.cpp
Normal file
576
Machines/Atari/ST/AtariST.cpp
Normal 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() {}
|
28
Machines/Atari/ST/AtariST.hpp
Normal file
28
Machines/Atari/ST/AtariST.hpp
Normal 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 */
|
232
Machines/Atari/ST/DMAController.cpp
Normal file
232
Machines/Atari/ST/DMAController.cpp
Normal 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);
|
||||
}
|
110
Machines/Atari/ST/DMAController.hpp
Normal file
110
Machines/Atari/ST/DMAController.hpp
Normal 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 */
|
354
Machines/Atari/ST/IntelligentKeyboard.cpp
Normal file
354
Machines/Atari/ST/IntelligentKeyboard.cpp
Normal 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
|
||||
});
|
||||
}
|
136
Machines/Atari/ST/IntelligentKeyboard.hpp
Normal file
136
Machines/Atari/ST/IntelligentKeyboard.hpp
Normal 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
500
Machines/Atari/ST/Video.cpp
Normal 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
126
Machines/Atari/ST/Video.hpp
Normal 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 */
|
@ -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_;
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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 */,
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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')
|
||||
|
@ -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_++;
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user