diff --git a/Analyser/Machines.hpp b/Analyser/Machines.hpp index 7159408f8..5c8fb6602 100644 --- a/Analyser/Machines.hpp +++ b/Analyser/Machines.hpp @@ -19,6 +19,7 @@ enum class Machine { AtariST, ColecoVision, Electron, + Enterprise, Macintosh, MasterSystem, MSX, diff --git a/Analyser/Static/Enterprise/StaticAnalyser.cpp b/Analyser/Static/Enterprise/StaticAnalyser.cpp new file mode 100644 index 000000000..7b701b1d9 --- /dev/null +++ b/Analyser/Static/Enterprise/StaticAnalyser.cpp @@ -0,0 +1,34 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 24/06/2021. +// Copyright 2021 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" +#include "Target.hpp" + +Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { + // This analyser can comprehend disks only. + if(media.disks.empty()) return {}; + + // Otherwise, for now: wave it through. + Analyser::Static::TargetList targets; + + using Target = Analyser::Static::Enterprise::Target; + auto *const target = new Target; + target->media = media; + + // Always require a BASIC. + target->basic_version = Target::BASICVersion::Any; + + // If this is a single-sided floppy disk, guess the Macintosh 512kb. + if(!media.disks.empty()) { + target->dos = Target::DOS::EXDOS; + } + + targets.push_back(std::unique_ptr(target)); + + return targets; +} diff --git a/Analyser/Static/Enterprise/StaticAnalyser.hpp b/Analyser/Static/Enterprise/StaticAnalyser.hpp new file mode 100644 index 000000000..0d7435d0a --- /dev/null +++ b/Analyser/Static/Enterprise/StaticAnalyser.hpp @@ -0,0 +1,27 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/06/2021. +// Copyright 2018 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp +#define Analyser_Static_Enterprise_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" +#include "../../../Storage/TargetPlatforms.hpp" +#include + +namespace Analyser { +namespace Static { +namespace Enterprise { + +TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); + +} +} +} + + +#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */ diff --git a/Analyser/Static/Enterprise/Target.hpp b/Analyser/Static/Enterprise/Target.hpp new file mode 100644 index 000000000..9200a3ce5 --- /dev/null +++ b/Analyser/Static/Enterprise/Target.hpp @@ -0,0 +1,50 @@ +// +// Target.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_Enterprise_Target_h +#define Analyser_Static_Enterprise_Target_h + +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" +#include "../StaticAnalyser.hpp" + +namespace Analyser { +namespace Static { +namespace Enterprise { + +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256); + ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any); + ReflectableEnum(BASICVersion, v10, v11, v21, Any, None); + ReflectableEnum(DOS, EXDOS, None); + + Model model = Model::Enterprise128; + EXOSVersion exos_version = EXOSVersion::Any; + BASICVersion basic_version = BASICVersion::None; + DOS dos = DOS::None; + + Target() : Analyser::Static::Target(Machine::Enterprise) { + if(needs_declare()) { + AnnounceEnum(Model); + AnnounceEnum(EXOSVersion); + AnnounceEnum(BASICVersion); + AnnounceEnum(DOS); + + DeclareField(model); + DeclareField(exos_version); + DeclareField(basic_version); + DeclareField(dos); + } + } +}; + +} +} +} + +#endif /* Analyser_Static_Enterprise_Target_h */ diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 589f3f206..7f88415d2 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -23,6 +23,7 @@ #include "Coleco/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" #include "DiskII/StaticAnalyser.hpp" +#include "Enterprise/StaticAnalyser.hpp" #include "Macintosh/StaticAnalyser.hpp" #include "MSX/StaticAnalyser.hpp" #include "Oric/StaticAnalyser.hpp" @@ -43,9 +44,9 @@ #include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp" #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" +#include "../../Storage/Disk/DiskImage/Formats/FAT12.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" #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" @@ -147,7 +148,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk) Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image) Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image) - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DSK (MSX) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DSK (MSX) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Oric) // DSK (Oric) Format("g64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // G64 Format( "hfe", @@ -157,6 +158,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, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) Format("image", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) + Format("img", result.disks, Disk::DiskImageHolder, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style) Format("msa", result.disks, Disk::DiskImageHolder, TargetPlatform::AtariST) // MSA Format("nib", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // NIB Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O @@ -256,6 +258,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { Append(Coleco); Append(Commodore); Append(DiskII); + Append(Enterprise); Append(Macintosh); Append(MSX); Append(Oric); diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index 1a3ddfdb9..6867b1898 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -213,7 +213,7 @@ class HalfCycles: public WrappedInt { /*! 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. + the value of @c this modulo @c divisor . @c this divided by @c divisor is returned. */ forceinline Cycles divide_cycles(const Cycles &divisor) { const HalfCycles half_divisor = HalfCycles(divisor); diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index 8d709559c..e7ff2696e 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -103,9 +103,9 @@ template ::value) { - time_until_event_ -= rhs; + time_until_event_ -= rhs * multiplier; if(time_until_event_ <= LocalTimeScale(0)) { - time_overrun_ = time_until_event_; + time_overrun_ = time_until_event_ / divider; flush(); update_sequence_point(); return true; @@ -145,13 +145,21 @@ template = time_until_event_; } + /// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly + /// after @c duration, if that delay were to occur in @c offset units of time from now. + [[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const { + // A 1:1 mapping is easy. + if constexpr (multiplier == 1 && divider == 1) { + return duration; + } + + // Work out when this query is placed, and the time to which it relates + const auto base = time_since_update_ + offset * divider; + const auto target = base + duration * divider; + + // Figure out the number of whole input steps that is required to get + // past target, and subtract the number of whole input steps necessary + // to get to base. + const auto steps_to_base = base.as_integral() / multiplier; + const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier; + + return LocalTimeScale(steps_to_target - steps_to_base); + } + /// Updates this template's record of the next sequence point. void update_sequence_point() { if constexpr (has_sequence_points::value) { - time_until_event_ = object_.get_next_sequence_point(); + // Keep a fast path where no conversions will be applied; if conversions are + // going to be applied then do a direct max -> max translation rather than + // allowing the arithmetic to overflow. + if constexpr (divider == 1 && std::is_same_v) { + time_until_event_ = object_.get_next_sequence_point(); + } else { + const auto time = object_.get_next_sequence_point(); + if(time == TargetTimeScale::max()) { + time_until_event_ = LocalTimeScale::max(); + } else { + time_until_event_ = time * divider; + } + } assert(time_until_event_ > LocalTimeScale(0)); } } diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 2845155cd..28c9d5e72 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) { goto test_type1_type; begin_type1_spin_up: - if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type; + if((command_&0x08) || get_drive().get_motor_on()) { + set_motor_on(true); + goto test_type1_type; + } SPIN_UP(); test_type1_type: @@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) { distance_into_section_ = 0; if((command_&0x08) && has_motor_on_line()) goto test_type2_delay; - if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay; + if(!has_motor_on_line() && !has_head_load_line()) { + if(has_motor_on_line()) set_motor_on(true); + goto test_type2_delay; + } if(has_motor_on_line()) goto begin_type2_spin_up; goto begin_type2_load_head; diff --git a/InstructionSets/CachingExecutor.hpp b/InstructionSets/CachingExecutor.hpp index 198fc2a7b..bc3204e2b 100644 --- a/InstructionSets/CachingExecutor.hpp +++ b/InstructionSets/CachingExecutor.hpp @@ -9,7 +9,7 @@ #ifndef CachingExecutor_hpp #define CachingExecutor_hpp -#include "Sizes.hpp" +#include "../Numeric/Sizes.hpp" #include #include diff --git a/InstructionSets/Disassembler.hpp b/InstructionSets/Disassembler.hpp index f590b3000..71f88d59e 100644 --- a/InstructionSets/Disassembler.hpp +++ b/InstructionSets/Disassembler.hpp @@ -9,7 +9,7 @@ #ifndef Disassembler_hpp #define Disassembler_hpp -#include "Sizes.hpp" +#include "../Numeric/Sizes.hpp" #include #include diff --git a/Machines/Apple/Macintosh/Keyboard.hpp b/Machines/Apple/Macintosh/Keyboard.hpp index e89840bdc..d69af1f56 100644 --- a/Machines/Apple/Macintosh/Keyboard.hpp +++ b/Machines/Apple/Macintosh/Keyboard.hpp @@ -271,7 +271,7 @@ class Keyboard { /// so this may not be valid prior to Mode::PerformingCommand. int command_ = 0; /// Populated during PerformingCommand as the response to the most-recently-received command, this - /// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, + /// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, /// but not afterwards. int response_ = 0; diff --git a/Machines/Enterprise/Dave.cpp b/Machines/Enterprise/Dave.cpp new file mode 100644 index 000000000..4f8f74ec8 --- /dev/null +++ b/Machines/Enterprise/Dave.cpp @@ -0,0 +1,313 @@ +// +// Dave.cpp +// Clock Signal +// +// Created by Thomas Harte on 22/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "Dave.hpp" + +using namespace Enterprise::Dave; + +// MARK: - Audio generator + +Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + +void Audio::write(uint16_t address, uint8_t value) { + address &= 0xf; + audio_queue_.defer([address, value, this] { + switch(address) { + case 0: case 2: case 4: + channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value; + break; + case 1: case 3: case 5: + channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8)); + channels_[address >> 1].distortion = Channel::Distortion((value >> 4)&3); + channels_[address >> 1].high_pass = value & 0x40; + channels_[address >> 1].ring_modulate = value & 0x80; + break; + case 6: + noise_.frequency = Noise::Frequency(value&3); + noise_.polynomial = Noise::Polynomial((value >> 2)&3); + noise_.swap_polynomial = value & 0x10; + noise_.low_pass = value & 0x20; + noise_.high_pass = value & 0x40; + noise_.ring_modulate = value & 0x80; + break; + + case 7: + channels_[0].sync = value & 0x01; + channels_[1].sync = value & 0x02; + channels_[2].sync = value & 0x04; + use_direct_output_[0] = value & 0x08; + use_direct_output_[1] = value & 0x10; + // Interrupt bits are handled separately. + break; + + case 8: case 9: case 10: + channels_[address - 8].amplitude[0] = value & 0x3f; + break; + case 12: case 13: case 14: + channels_[address - 12].amplitude[1] = value & 0x3f; + break; + case 11: noise_.amplitude[0] = value & 0x3f; break; + case 15: noise_.amplitude[1] = value & 0x3f; break; + } + }); +} + +void Audio::set_sample_volume_range(int16_t range) { + audio_queue_.defer([range, this] { + volume_ = range / (63*4); + }); +} + +void Audio::update_channel(int c) { + if(channels_[c].sync) { + channels_[c].count = channels_[c].reload; + channels_[c].output <<= 1; + return; + } + + auto output = channels_[c].output & 1; + channels_[c].output <<= 1; + if(!channels_[c].count) { + channels_[c].count = channels_[c].reload; + + if(channels_[c].distortion == Channel::Distortion::None) + output ^= 1; + else + output = poly_state_[int(channels_[c].distortion)]; + + if(channels_[c].high_pass && (channels_[(c+1)%3].output&3) == 2) { + output = 0; + } + if(channels_[c].ring_modulate) { + output = ~(output ^ channels_[(c+2)%3].output) & 1; + } + } else { + --channels_[c].count; + } + + channels_[c].output |= output; +} + +void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { + for(size_t c = 0; c < number_of_samples; c++) { + poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next(); + poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next(); + poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next(); + if(noise_.swap_polynomial) { + poly_state_[int(Channel::Distortion::SevenBit)] = poly_state_[int(Channel::Distortion::None)]; + } + + // Update tone channels. + update_channel(0); + update_channel(1); + update_channel(2); + + // Update noise channel. + + // Step 1: decide whether there is a tick to apply. + bool noise_tick = false; + if(noise_.frequency == Noise::Frequency::DivideByFour) { + if(!noise_.count) { + noise_tick = true; + noise_.count = 3; + } else { + --noise_.count; + } + } else { + noise_tick = (channels_[int(noise_.frequency) - 1].output&3) == 2; + } + + // Step 2: tick if necessary. + if(noise_tick) { + switch(noise_.polynomial) { + case Noise::Polynomial::SeventeenBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly17_.next()); + break; + case Noise::Polynomial::FifteenBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly15_.next()); + break; + case Noise::Polynomial::ElevenBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly11_.next()); + break; + case Noise::Polynomial::NineBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly9_.next()); + break; + } + + noise_.output <<= 1; + noise_.output |= poly_state_[int(Channel::Distortion::None)]; + + // Low pass: sample channel 2 on downward transitions of the prima facie output. + if(noise_.low_pass) { + if((noise_.output & 3) == 2) { + noise_.output = (noise_.output & ~1) | (channels_[2].output & 1); + } else { + noise_.output = (noise_.output & ~1) | (noise_.output & 1); + } + } + } + + // Apply noise high-pass at the rate of the tone channels. + if(noise_.high_pass && (channels_[0].output & 3) == 2) { + noise_.output &= ~1; + } + + // Update noise ring modulation, if any. + if(noise_.ring_modulate) { + noise_.final_output = !((noise_.output ^ channels_[1].output) & 1); + } else { + noise_.final_output = noise_.output & 1; + } + + // I'm unclear on the details of the time division multiplexing so, + // for now, just sum the outputs. + target[(c << 1) + 0] = + volume_ * + (use_direct_output_[0] ? + channels_[0].amplitude[0] + : ( + channels_[0].amplitude[0] * (channels_[0].output & 1) + + channels_[1].amplitude[0] * (channels_[1].output & 1) + + channels_[2].amplitude[0] * (channels_[2].output & 1) + + noise_.amplitude[0] * noise_.final_output + )); + + target[(c << 1) + 1] = + volume_ * + (use_direct_output_[1] ? + channels_[0].amplitude[1] + : ( + channels_[0].amplitude[1] * (channels_[0].output & 1) + + channels_[1].amplitude[1] * (channels_[1].output & 1) + + channels_[2].amplitude[1] * (channels_[2].output & 1) + + noise_.amplitude[1] * noise_.final_output + )); + } +} + +// MARK: - Interrupt source + +uint8_t TimedInterruptSource::get_new_interrupts() { + const uint8_t result = interrupts_; + interrupts_ = 0; + return result; +} + +void TimedInterruptSource::write(uint16_t address, uint8_t value) { + address &= 15; + switch(address) { + default: break; + + case 0: case 2: + channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value; + break; + case 1: case 3: + channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8)); + break; + + case 7: { + channels_[0].sync = value & 0x01; + channels_[1].sync = value & 0x02; + + const InterruptRate rate = InterruptRate((value >> 5) & 3); + if(rate != rate_) { + rate_ = InterruptRate((value >> 5) & 3); + + if(rate_ >= InterruptRate::ToneGenerator0) { + programmable_level_ = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)].level; + } else { + programmable_offset_ = programmble_reload(rate_); + } + } + } break; + } +} + +void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement) { + if(channels_[c].sync) { + channels_[c].value = channels_[c].reload; + } else { + if(decrement <= channels_[c].value) { + channels_[c].value -= decrement; + } else { + const int num_flips = (decrement - channels_[c].value) / (channels_[c].reload + 1); + if(is_linked && num_flips + channels_[c].level >= 2) { + interrupts_ |= uint8_t(Interrupt::VariableFrequency); + } + channels_[c].level ^= (num_flips & 1); + channels_[c].value = (decrement - channels_[c].value) % (channels_[c].reload + 1); + } + } +} + +void TimedInterruptSource::run_for(Cycles cycles) { + // Update the 1Hz interrupt. + one_hz_offset_ -= cycles; + if(one_hz_offset_ <= Cycles(0)) { + interrupts_ |= uint8_t(Interrupt::OneHz); + one_hz_offset_ += clock_rate; + } + + // Update the two tone channels. + update_channel(0, rate_ == InterruptRate::ToneGenerator0, cycles.as()); + update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as()); + + // Update the programmable-frequency interrupt. + if(rate_ < InterruptRate::ToneGenerator0) { + programmable_offset_ -= cycles.as(); + if(programmable_offset_ <= 0) { + if(programmable_level_) { + interrupts_ |= uint8_t(Interrupt::VariableFrequency); + } + programmable_level_ ^= true; + programmable_offset_ = programmble_reload(rate_); + } + } +} + +Cycles TimedInterruptSource::get_next_sequence_point() const { + int result = one_hz_offset_.as(); + + switch(rate_) { + case InterruptRate::OnekHz: + case InterruptRate::FiftyHz: + result = std::min(result, programmable_offset_); + break; + case InterruptRate::ToneGenerator0: + case InterruptRate::ToneGenerator1: { + const auto& channel = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)]; + const int cycles_until_interrupt = (channel.value + 1) + (channel.level ? 0 : channel.reload + 1); + result = std::min(result, cycles_until_interrupt); + } break; + } + + return Cycles(result); +} + +uint8_t TimedInterruptSource::get_divider_state() { + bool programmable_flag = false; + switch(rate_) { + case InterruptRate::OnekHz: + case InterruptRate::FiftyHz: + programmable_flag = programmable_level_; + break; + case InterruptRate::ToneGenerator0: + programmable_flag = channels_[0].level; + break; + case InterruptRate::ToneGenerator1: + programmable_flag = channels_[1].level; + break; + } + + // one_hz_offset_ counts downwards, so when it crosses the halfway mark + // it enters the high part of its wave. + return + (one_hz_offset_ < half_clock_rate ? 0x4 : 0x0) | + (programmable_flag ? 0x1 : 0x0); +} diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp new file mode 100644 index 000000000..a280665af --- /dev/null +++ b/Machines/Enterprise/Dave.hpp @@ -0,0 +1,166 @@ +// +// Dave.hpp +// Clock Signal +// +// Created by Thomas Harte on 22/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Dave_hpp +#define Dave_hpp + +#include + +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" +#include "../../Numeric/LFSR.hpp" +#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" + +namespace Enterprise { +namespace Dave { + +enum class Interrupt: uint8_t { + VariableFrequency = 0x02, + OneHz = 0x08, + Nick = 0x20, +}; + +/*! + Models the audio-production subset of Dave's behaviour. +*/ +class Audio: public Outputs::Speaker::SampleSource { + public: + Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue); + + void write(uint16_t address, uint8_t value); + + // MARK: - SampleSource. + void set_sample_volume_range(int16_t range); + static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound. + void get_samples(std::size_t number_of_samples, int16_t *target); + + private: + Concurrency::DeferringAsyncTaskQueue &audio_queue_; + + // Tone channels. + struct Channel { + // User-set values. + uint16_t reload = 0; + bool high_pass = false; + bool ring_modulate = false; + enum class Distortion { + None = 0, + FourBit = 1, + FiveBit = 2, + SevenBit = 3, + } distortion = Distortion::None; + uint8_t amplitude[2]{}; + bool sync = false; + + // Current state. + uint16_t count = 0; + int output = 0; + } channels_[3]; + void update_channel(int); + + // Noise channel. + struct Noise { + // User-set values. + uint8_t amplitude[2]{}; + enum class Frequency { + DivideByFour, + ToneChannel0, + ToneChannel1, + ToneChannel2, + } frequency = Frequency::DivideByFour; + enum class Polynomial { + SeventeenBit, + FifteenBit, + ElevenBit, + NineBit + } polynomial = Polynomial::SeventeenBit; + bool swap_polynomial = false; + bool low_pass = false; + bool high_pass = false; + bool ring_modulate = false; + + // Current state. + int count = 0; + int output = false; + bool final_output = false; + } noise_; + void update_noise(); + + bool use_direct_output_[2]{}; + + // Global volume, per SampleSource obligations. + int16_t volume_ = 0; + + // Polynomials that are always running. + Numeric::LFSRv<0xc> poly4_; + Numeric::LFSRv<0x14> poly5_; + Numeric::LFSRv<0x60> poly7_; + + // The selectable, noise-related polynomial. + Numeric::LFSRv<0x110> poly9_; + Numeric::LFSRv<0x500> poly11_; + Numeric::LFSRv<0x6000> poly15_; + Numeric::LFSRv<0x12000> poly17_; + + // Current state of the active polynomials. + uint8_t poly_state_[4]; +}; + +/*! + Provides Dave's timed interrupts — those that are provided at 1 kHz, + 50 Hz or according to the rate of tone generators 0 or 1, plus the fixed + 1 Hz interrupt. + +*/ +class TimedInterruptSource { + public: + void write(uint16_t address, uint8_t value); + + uint8_t get_new_interrupts(); + uint8_t get_divider_state(); + + void run_for(Cycles); + + Cycles get_next_sequence_point() const; + + private: + uint8_t interrupts_ = 0; + + static constexpr Cycles clock_rate{250000}; + static constexpr Cycles half_clock_rate{125000}; + + Cycles one_hz_offset_ = clock_rate; + + enum class InterruptRate { + OnekHz, + FiftyHz, + ToneGenerator0, + ToneGenerator1, + } rate_ = InterruptRate::OnekHz; + int programmable_offset_ = programmble_reload(InterruptRate::OnekHz); + bool programmable_level_ = false; + + struct Channel { + int value = 100, reload = 100; + bool sync = false; + bool level = false; + } channels_[2]; + void update_channel(int c, bool is_linked, int decrement); + static constexpr int programmble_reload(InterruptRate rate) { + switch(rate) { + default: return 0; + case InterruptRate::OnekHz: return 125; + case InterruptRate::FiftyHz: return 2500; + } + } +}; + +} +} + +#endif /* Dave_hpp */ diff --git a/Machines/Enterprise/EXDos.cpp b/Machines/Enterprise/EXDos.cpp new file mode 100644 index 000000000..1bc80ef41 --- /dev/null +++ b/Machines/Enterprise/EXDos.cpp @@ -0,0 +1,83 @@ +// +// EXDos.cpp +// Clock Signal +// +// Created by Thomas Harte on 20/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "EXDos.hpp" + +using namespace Enterprise; + +EXDos::EXDos() : WD1770(P1770) { + emplace_drives(4, 8000000, 300, 2); + set_control_register(0x00); +} + +void EXDos::set_disk(std::shared_ptr disk, size_t drive) { + get_drive(drive).set_disk(disk); + disk_did_change_ = true; +} + +// Documentation for the control register: +// +// Write: +// b7 in use (???) +// b6 disk change reset +// b5 0 = double density, 1 = single density +// b4 side 1 select +// b3, b3, b1, b0 select drive 3, 2, 1, 0 +// +// Read: +// b7 data request from WD1770 +// b6 disk change +// b5, b4, b3, b2: not used +// b1 interrupt request from WD1770 +// b0 drive ready + +void EXDos::set_control_register(uint8_t control) { +// printf("Set control: %02x\n", control); + + if(control & 0x40) disk_did_change_ = false; + set_is_double_density(!(control & 0x20)); + + // Set side. + const int head = (control >> 4) & 1; + for(size_t c = 0; c < 4; c++) { + get_drive(c).set_head(head); + } + + // Select drive, ensuring handover of the motor-on state. + const bool motor_state = get_drive().get_motor_on(); + for_all_drives([] (Storage::Disk::Drive &drive, size_t) { + drive.set_motor_on(false); + }); + set_drive(control & 0xf); + get_drive().set_motor_on(motor_state); +} + +uint8_t EXDos::get_control_register() { + // TODO: how does disk_did_change_ really work? Presumably + // it latches RDY? + const uint8_t status = + (get_data_request_line() ? 0x80 : 0x00) | + (disk_did_change_ ? 0x40 : 0x00) | + (get_interrupt_request_line() ? 0x02 : 0x00) | + (get_drive().get_is_ready() ? 0x01 : 0x00); + +// printf("Get status: %02x\n", status); + return status; +} + +void EXDos::set_motor_on(bool on) { + // TODO: this status should transfer if the selected drive changes. But the same goes for + // writing state, so plenty of work to do in general here. + get_drive().set_motor_on(on); +} + +void EXDos::set_activity_observer(Activity::Observer *observer) { + for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) { + drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true); + }); +} diff --git a/Machines/Enterprise/EXDos.hpp b/Machines/Enterprise/EXDos.hpp new file mode 100644 index 000000000..33553be6a --- /dev/null +++ b/Machines/Enterprise/EXDos.hpp @@ -0,0 +1,36 @@ +// +// EXDos.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef EXDos_hpp +#define EXDos_hpp + +#include "../../Components/1770/1770.hpp" +#include "../../Activity/Observer.hpp" + +namespace Enterprise { + +class EXDos final : public WD::WD1770 { + public: + EXDos(); + + void set_disk(std::shared_ptr disk, size_t drive); + + void set_control_register(uint8_t control); + uint8_t get_control_register(); + + void set_activity_observer(Activity::Observer *observer); + + private: + bool disk_did_change_ = false; + + void set_motor_on(bool on) override; +}; + +} + +#endif /* EXDos_hpp */ diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp new file mode 100644 index 000000000..ef2752d51 --- /dev/null +++ b/Machines/Enterprise/Enterprise.cpp @@ -0,0 +1,647 @@ +// +// Enterprise.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "Enterprise.hpp" + +#include "Dave.hpp" +#include "EXDos.hpp" +#include "Keyboard.hpp" +#include "Nick.hpp" + +#include "../MachineTypes.hpp" + +#include "../../Analyser/Static/Enterprise/Target.hpp" +#include "../../ClockReceiver/JustInTime.hpp" +#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../Processors/Z80/Z80.hpp" + +namespace Enterprise { + +/* + Notes to self on timing: + + Nick divides each line into 57 windows; each window lasts 16 cycles and dedicates the + first 10 of those to VRAM accesses, leaving the final six for a Z80 video RAM access + if one has been requested. + + The Z80 has a separate, asynchronous 4Mhz clock. That's that. + + The documentation is also very forward in emphasising that Nick generates phaselocked + (i.e. in-phase) PAL video. + + So: 57*16 = 912 cycles/line. + + A standard PAL line lasts 64µs and during that time outputs 283.7516 colour cycles. + + I shall _guess_ that the Enterprise stretches each line to 284 colour cycles rather than + reducing it to 283. + + Therefore 912 cycles occurs in 284/283.7516 * 64 µs. + + So one line = 181760000 / 2837516 µs = 45440000 / 709379 µs + => one cycle = 45440000 / 709379*912 = 45440000 / 646953648 = 2840000 / 40434603 µs + => clock rate of 40434603 / 2840000 Mhz + + And, therefore, the ratio to a 4Mhz Z80 clock is: + + 40434603 / (2840000 * 4) + = 40434603 / 11360000 + i.e. roughly 3.55 Nick cycles per Z80 cycle. + + If that's true then the 6-cycle window is around 1.69 Z80 cycles long. Given that the Z80 + clock in an Enterprise can be stopped in half-cycle increments only, the Z80 can only be + guaranteed to have around a 1.19 cycle minimum for its actual access. I'm therefore further + postulating that the clock stoppage takes place so as to align the final cycle of a relevant + access over the available window. + +*/ + +template class ConcreteMachine: + public Activity::Source, + public CPU::Z80::BusHandler, + public Machine, + public MachineTypes::AudioProducer, + public MachineTypes::MappedKeyboardMachine, + public MachineTypes::MediaTarget, + public MachineTypes::ScanProducer, + public MachineTypes::TimedMachine { + private: + constexpr uint8_t min_ram_slot(const Analyser::Static::Enterprise::Target &target) { + size_t ram_size = 128*1024; + switch(target.model) { + case Analyser::Static::Enterprise::Target::Model::Enterprise64: ram_size = 64*1024; break; + case Analyser::Static::Enterprise::Target::Model::Enterprise128: ram_size = 128*1024; break; + case Analyser::Static::Enterprise::Target::Model::Enterprise256: ram_size = 256*1024; break; + } + + return uint8_t(0x100 - ram_size / 0x4000); + } + + public: + ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + min_ram_slot_(min_ram_slot(target)), + z80_(*this), + nick_(ram_.end() - 65536), + dave_audio_(audio_queue_), + speaker_(dave_audio_) { + // Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere. + set_clock_rate(4'000'000); + + ROM::Request request; + using Target = Analyser::Static::Enterprise::Target; + + // Pick one or more EXOS ROMs. + switch(target.exos_version) { + case Target::EXOSVersion::v10: request = request && ROM::Request(ROM::Name::EnterpriseEXOS10); break; + case Target::EXOSVersion::v20: request = request && ROM::Request(ROM::Name::EnterpriseEXOS20); break; + case Target::EXOSVersion::v21: request = request && ROM::Request(ROM::Name::EnterpriseEXOS21); break; + case Target::EXOSVersion::v23: request = request && ROM::Request(ROM::Name::EnterpriseEXOS23); break; + case Target::EXOSVersion::Any: + request = + request && ( + ROM::Request(ROM::Name::EnterpriseEXOS10) || ROM::Request(ROM::Name::EnterpriseEXOS20) || + ROM::Request(ROM::Name::EnterpriseEXOS21) || ROM::Request(ROM::Name::EnterpriseEXOS23) + ); + break; + + default: break; + } + + // Similarly pick one or more BASIC ROMs. + switch(target.basic_version) { + case Target::BASICVersion::v10: + request = request && ( + ROM::Request(ROM::Name::EnterpriseBASIC10) || + (ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2)) + ); + break; + case Target::BASICVersion::v11: + request = request && ( + ROM::Request(ROM::Name::EnterpriseBASIC11) || + ROM::Request(ROM::Name::EnterpriseBASIC11Suffixed) + ); + case Target::BASICVersion::v21: + request = request && ROM::Request(ROM::Name::EnterpriseBASIC21); + break; + case Target::BASICVersion::Any: + request = + request && ( + ROM::Request(ROM::Name::EnterpriseBASIC10) || + (ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2)) || + ROM::Request(ROM::Name::EnterpriseBASIC11) || + ROM::Request(ROM::Name::EnterpriseBASIC21) + ); + break; + + default: break; + } + + // Possibly add in a DOS. + switch(target.dos) { + case Target::DOS::EXDOS: request = request && ROM::Request(ROM::Name::EnterpriseEXDOS); break; + default: break; + } + + // Get and validate ROMs. + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + + // Extract the appropriate EXOS ROM. + exos_.fill(0xff); + for(const auto rom_name: { ROM::Name::EnterpriseEXOS10, ROM::Name::EnterpriseEXOS20, ROM::Name::EnterpriseEXOS21, ROM::Name::EnterpriseEXOS23 }) { + const auto exos = roms.find(rom_name); + if(exos != roms.end()) { + memcpy(exos_.data(), exos->second.data(), std::min(exos_.size(), exos->second.size())); + break; + } + } + + // Extract the appropriate BASIC ROM[s] (if any). + basic_.fill(0xff); + bool has_basic = false; + for(const auto rom_name: { ROM::Name::EnterpriseBASIC10, ROM::Name::EnterpriseBASIC11, ROM::Name::EnterpriseBASIC11Suffixed, ROM::Name::EnterpriseBASIC21 }) { + const auto basic = roms.find(rom_name); + if(basic != roms.end()) { + memcpy(basic_.data(), basic->second.data(), std::min(basic_.size(), basic->second.size())); + has_basic = true; + break; + } + } + if(!has_basic) { + const auto basic1 = roms.find(ROM::Name::EnterpriseBASIC10Part1); + const auto basic2 = roms.find(ROM::Name::EnterpriseBASIC10Part2); + if(basic1 != roms.end() && basic2 != roms.end()) { + memcpy(&basic_[0x0000], basic1->second.data(), std::min(size_t(8192), basic1->second.size())); + memcpy(&basic_[0x2000], basic2->second.data(), std::min(size_t(8192), basic2->second.size())); + } + } + + // Extract the appropriate DOS ROMs. + epdos_rom_.fill(0xff); + const auto epdos = roms.find(ROM::Name::EnterpriseEPDOS); + if(epdos != roms.end()) { + memcpy(epdos_rom_.data(), epdos->second.data(), std::min(epdos_rom_.size(), epdos->second.size())); + } + exdos_rom_.fill(0xff); + const auto exdos = roms.find(ROM::Name::EnterpriseEXDOS); + if(exdos != roms.end()) { + memcpy(exdos_rom_.data(), exdos->second.data(), std::min(exdos_rom_.size(), exdos->second.size())); + } + + // Seed key state. + clear_all_keys(); + + // Take a reasonable guess at the initial memory configuration: + // put EXOS into the first bank since this is a Z80 and therefore + // starts from address 0; the third instruction in EXOS is a jump + // to $c02e so it's reasonable to assume EXOS is in the highest bank + // too, and it appears to act correctly if it's the first 16kb that's + // in the highest bank. From there I guess: all banks are initialised + // to 0. + page<0>(0x00); + page<1>(0x00); + page<2>(0x00); + page<3>(0x00); + + // Set up audio. + speaker_.set_input_rate(250000.0f); // TODO: a bigger number, and respect the programmable divider. + + // Pass on any media. + insert_media(target.media); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + // MARK: - Z80::BusHandler. + forceinline void advance_nick(HalfCycles duration) { + if(nick_ += duration) { + const auto nick = nick_.last_valid(); + const bool nick_interrupt_line = nick->get_interrupt_line(); + if(nick_interrupt_line && !previous_nick_interrupt_line_) { + set_interrupts(uint8_t(Dave::Interrupt::Nick), nick_.last_sequence_point_overrun()); + } + previous_nick_interrupt_line_ = nick_interrupt_line; + } + } + + forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + using PartialMachineCycle = CPU::Z80::PartialMachineCycle; + const uint16_t address = cycle.address ? *cycle.address : 0x0000; + + // Calculate an access penalty, if applicable. + // + // Rule applied here, which is slightly inferred: + // + // Non-video reads and writes are delayed by exactly a cycle or not delayed at all, + // depending on the programmer's configuration of Dave. + // + // Video reads and writes, and Nick port accesses, are delayed so that the last + // clock cycle of the machine cycle falls wholly inside the designated Z80 access + // window, per Nick. + // + // The switch statement below just attempts to implement that logic. + // + HalfCycles penalty; + switch(cycle.operation) { + default: break; + + // For non-video pauses, insert during the initial part of the bus cycle. + case CPU::Z80::PartialMachineCycle::ReadStart: + case CPU::Z80::PartialMachineCycle::WriteStart: + if(!is_video_[address >> 14] && wait_mode_ == WaitMode::OnAllAccesses) { + penalty = HalfCycles(2); + } + break; + case CPU::Z80::PartialMachineCycle::ReadOpcodeStart: + if(!is_video_[address >> 14] && wait_mode_ != WaitMode::None) { + penalty = HalfCycles(2); + } else { + // Query Nick for the amount of delay that would occur with one cycle left + // in this read opcode. + const auto delay_time = nick_.time_since_flush(HalfCycles(2)); + const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time); + penalty = nick_.back_map(delay, delay_time); + } + break; + + // Video pauses: insert right at the end of the bus cycle. + case CPU::Z80::PartialMachineCycle::Write: + // Ensure all video that should have been collected prior to + // this write has been. + if(is_video_[address >> 14]) { + nick_.flush(); + } + [[fallthrough]]; + + case CPU::Z80::PartialMachineCycle::Read: + if(is_video_[address >> 14]) { + // Get delay, in Nick cycles, for a Z80 access that occurs in 0.5 + // cycles from now (i.e. with one cycle left to run). + const auto delay_time = nick_.time_since_flush(HalfCycles(1)); + const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time); + penalty = nick_.back_map(delay, delay_time); + } + break; + + case CPU::Z80::PartialMachineCycle::Input: + case CPU::Z80::PartialMachineCycle::Output: { + if((address & 0xf0) == 0x80) { + // Get delay, in Nick cycles, for a Z80 access that occurs in 0.5 + // cycles from now (i.e. with one cycle left to run). + const auto delay_time = nick_.time_since_flush(HalfCycles(1)); + const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time); + penalty = nick_.back_map(delay, delay_time); + } + } + } + + const HalfCycles full_length = cycle.length + penalty; + time_since_audio_update_ += full_length; + advance_nick(full_length); + if(dave_timer_ += full_length) { + set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun()); + } + + // The WD/etc runs at a nominal 8Mhz. + if constexpr (has_disk_controller) { + exdos_.run_for(Cycles(full_length.as_integral())); + } + + switch(cycle.operation) { + default: break; + + case CPU::Z80::PartialMachineCycle::Input: + switch(address & 0xff) { + default: + printf("Unhandled input: %04x\n", address); +// assert(false); + *cycle.value = 0xff; + break; + + case 0x10: case 0x11: case 0x12: case 0x13: + case 0x14: case 0x15: case 0x16: case 0x17: + if constexpr (has_disk_controller) { + *cycle.value = exdos_.read(address); + } else { + *cycle.value = 0xff; + } + break; + case 0x18: case 0x19: case 0x1a: case 0x1b: + case 0x1c: case 0x1d: case 0x1e: case 0x1f: + if constexpr (has_disk_controller) { + *cycle.value = exdos_.get_control_register(); + } else { + *cycle.value = 0xff; + } + break; + + case 0xb0: *cycle.value = pages_[0]; break; + case 0xb1: *cycle.value = pages_[1]; break; + case 0xb2: *cycle.value = pages_[2]; break; + case 0xb3: *cycle.value = pages_[3]; break; + + case 0xb4: + *cycle.value = + (nick_->get_interrupt_line() ? 0x00 : 0x10) | + dave_timer_->get_divider_state() | + interrupt_state_; + break; + case 0xb5: + if(active_key_line_ < key_lines_.size()) { + *cycle.value = key_lines_[active_key_line_]; + } else { + *cycle.value = 0xff; + } + break; + case 0xb6: { + // TODO: selected keyboard row, 0 to 9, should return one bit of joystick + // input. That being the case: + // + // b0: joystick input + // b1, b2: unused (in theory read from control port, but not used by any hardware) + // b3: 0 = printer ready; 1 = not ready + // b4: serial, data in + // b5: serial, status in + // b6: tape input volume level, 0 = high, 1 = low + // b7: tape data input + *cycle.value = 0xff; + } break; + } + break; + + case CPU::Z80::PartialMachineCycle::Output: + switch(address & 0xff) { + default: + printf("Unhandled output: %04x\n", address); +// assert(false); + break; + + case 0x10: case 0x11: case 0x12: case 0x13: + case 0x14: case 0x15: case 0x16: case 0x17: + if constexpr(has_disk_controller) { + exdos_.write(address, *cycle.value); + } + break; + case 0x18: case 0x19: case 0x1a: case 0x1b: + case 0x1c: case 0x1d: case 0x1e: case 0x1f: + if constexpr(has_disk_controller) { + exdos_.set_control_register(*cycle.value); + } + break; + + case 0x80: case 0x81: case 0x82: case 0x83: + case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8a: case 0x8b: + case 0x8c: case 0x8d: case 0x8e: case 0x8f: + nick_->write(address, *cycle.value); + break; + + case 0xb0: page<0>(*cycle.value); break; + case 0xb1: page<1>(*cycle.value); break; + case 0xb2: page<2>(*cycle.value); break; + case 0xb3: page<3>(*cycle.value); break; + + case 0xa0: case 0xa1: case 0xa2: case 0xa3: + case 0xa4: case 0xa5: case 0xa6: case 0xa7: + case 0xa8: case 0xa9: case 0xaa: case 0xab: + case 0xac: case 0xad: case 0xae: case 0xaf: + update_audio(); + dave_audio_.write(address, *cycle.value); + dave_timer_->write(address, *cycle.value); + break; + + case 0xb4: + interrupt_mask_ = *cycle.value & 0x55; + interrupt_state_ &= ~*cycle.value; + update_interrupts(); + break; + case 0xb5: + active_key_line_ = *cycle.value & 0xf; + // TODO: + // + // b4: strobe output for printer + // b5: tape sound control (?) + // b6: tape motor control 1, 1 = on + // b7: tape motor control 2, 1 = on + break; + case 0xb6: + // Just 8 bits of printer data. + printf("TODO: printer output %02x\n", *cycle.value); + break; + case 0xb7: + // b0 = serial data out + // b1 = serial status out + printf("TODO: serial output %02x\n", *cycle.value); + break; + case 0xbf: + // TODO: onboard RAM, Dave 8/12Mhz select. + switch((*cycle.value >> 2)&3) { + default: wait_mode_ = WaitMode::None; break; + case 0: wait_mode_ = WaitMode::OnAllAccesses; break; + case 1: wait_mode_ = WaitMode::OnM1; break; + } + break; + } + break; + + case CPU::Z80::PartialMachineCycle::Read: + case CPU::Z80::PartialMachineCycle::ReadOpcode: + if(read_pointers_[address >> 14]) { + *cycle.value = read_pointers_[address >> 14][address]; + } else { + *cycle.value = 0xff; + } + break; + + case CPU::Z80::PartialMachineCycle::Write: + if(write_pointers_[address >> 14]) { + write_pointers_[address >> 14][address] = *cycle.value; + } + break; + } + + return penalty; + } + + void flush() { + nick_.flush(); + update_audio(); + audio_queue_.perform(); + } + + private: + // MARK: - Memory layout + std::array ram_{}; + std::array exos_; + std::array basic_; + std::array exdos_rom_; + std::array epdos_rom_; + const uint8_t min_ram_slot_; + + const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr}; + uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr}; + uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80}; + + template void page(uint8_t offset) { + pages_[slot] = offset; + +#define Map(location, source) \ + if(offset >= location && offset < location + source.size() / 0x4000) { \ + page(&source[(offset - location) * 0x4000], nullptr); \ + is_video_[slot] = false; \ + return; \ + } + + Map(0, exos_); + Map(16, basic_); + Map(32, exdos_rom_); + Map(48, epdos_rom_); + +#undef Map + + // Of whatever size of RAM I've declared above, use only the final portion. + // This correlated with Nick always having been handed the final 64kb and, + // at least while the RAM is the first thing declared above, does a little + // to benefit data locality. Albeit not in a useful sense. + if(offset >= min_ram_slot_) { + const auto ram_floor = 4194304 - ram_.size(); + const size_t address = offset * 0x4000 - ram_floor; + is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption. + page(&ram_[address], &ram_[address]); + return; + } + + page(nullptr, nullptr); + } + + template void page(const uint8_t *read, uint8_t *write) { + read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr; + write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr; + } + + // MARK: - Memory Timing + + // The wait mode affects all memory accesses _outside of the video area_. + enum class WaitMode { + None, + OnM1, + OnAllAccesses + } wait_mode_ = WaitMode::None; + bool is_video_[4]{}; + + // MARK: - ScanProducer + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + nick_.last_valid()->set_scan_target(scan_target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const override { + return nick_.last_valid()->get_scaled_scan_status(); + } + + // MARK: - AudioProducer + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + // MARK: - TimedMachine + void run_for(const Cycles cycles) override { + z80_.run_for(cycles); + } + + // MARK: - KeyboardMachine + Enterprise::KeyboardMapper keyboard_mapper_; + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + + uint8_t active_key_line_ = 0; + std::array key_lines_; + void set_key_state(uint16_t key, bool is_pressed) final { + if(is_pressed) { + key_lines_[key >> 8] &= ~uint8_t(key); + } else { + key_lines_[key >> 8] |= uint8_t(key); + } + } + + void clear_all_keys() final { + key_lines_.fill(0xff); + } + + // MARK: - MediaTarget + bool insert_media(const Analyser::Static::Media &media) final { + if constexpr (has_disk_controller) { + if(!media.disks.empty()) { + exdos_.set_disk(media.disks.front(), 0); + } + } + + return true; + } + + // MARK: - Interrupts + + uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00; + void set_interrupts(uint8_t mask, HalfCycles offset = HalfCycles(0)) { + interrupt_state_ |= uint8_t(mask); + update_interrupts(offset); + } + void update_interrupts(HalfCycles offset = HalfCycles(0)) { + z80_.set_interrupt_line((interrupt_state_ >> 1) & interrupt_mask_, offset); + } + + // MARK: - Chips. + CPU::Z80::Processor z80_; + JustInTimeActor nick_; + bool previous_nick_interrupt_line_ = false; + // Cf. timing guesses above. + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + Dave::Audio dave_audio_; + Outputs::Speaker::LowpassSpeaker speaker_; + HalfCycles time_since_audio_update_; + + // The following two should both use the same divider. + JustInTimeActor dave_timer_; + inline void update_audio() { + // TODO: divide by only 8, letting Dave divide itself by a further 2 or 3 + // as per its own register. + speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16))); + } + + // MARK: - EXDos card. + EXDos exdos_; + + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) final { + if constexpr (has_disk_controller) { + exdos_.set_activity_observer(observer); + } + } +}; + +} + +using namespace Enterprise; + +Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { + using Target = Analyser::Static::Enterprise::Target; + const Target *const enterprise_target = dynamic_cast(target); + + if(enterprise_target->dos == Target::DOS::None) + return new Enterprise::ConcreteMachine(*enterprise_target, rom_fetcher); + else + return new Enterprise::ConcreteMachine(*enterprise_target, rom_fetcher); +} + +Machine::~Machine() {} diff --git a/Machines/Enterprise/Enterprise.hpp b/Machines/Enterprise/Enterprise.hpp new file mode 100644 index 000000000..3a42a70f5 --- /dev/null +++ b/Machines/Enterprise/Enterprise.hpp @@ -0,0 +1,27 @@ +// +// Enterprise.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Enterprise_hpp +#define Enterprise_hpp + +#include "../../Analyser/Static/StaticAnalyser.hpp" +#include "../ROMMachine.hpp" + +namespace Enterprise { + +class Machine { + public: + virtual ~Machine(); + + static Machine *Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + +}; + +}; + +#endif /* Enterprise_hpp */ diff --git a/Machines/Enterprise/Keyboard.cpp b/Machines/Enterprise/Keyboard.cpp new file mode 100644 index 000000000..4c2444df3 --- /dev/null +++ b/Machines/Enterprise/Keyboard.cpp @@ -0,0 +1,74 @@ +// +// Keyboard.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "Keyboard.hpp" + +using namespace Enterprise; + +uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const { +#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest) + switch(key) { + default: break; + + BIND(Backslash, Backslash); + BIND(CapsLock, Lock); + BIND(Tab, Tab); + BIND(Escape, Escape); + BIND(Hyphen, Hyphen); + BIND(Equals, Tilde); + BIND(Backspace, Erase); + BIND(Delete, Delete); + BIND(Semicolon, Semicolon); + BIND(Quote, Colon); + BIND(OpenSquareBracket, OpenSquareBracket); + BIND(CloseSquareBracket, CloseSquareBracket); + + BIND(End, Stop); + BIND(Insert, Insert); + BIND(BackTick, At); + + BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4); BIND(k5, k5); + BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9); BIND(k0, k0); + + BIND(F1, F1); BIND(F2, F2); BIND(F3, F3); BIND(F4, F4); + BIND(F5, F5); BIND(F6, F6); BIND(F7, F7); BIND(F8, F8); + + BIND(Keypad1, F1); BIND(Keypad2, F2); BIND(Keypad3, F3); BIND(Keypad4, F4); + BIND(Keypad5, F5); BIND(Keypad6, F6); BIND(Keypad7, F7); BIND(Keypad8, F8); + + BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T); + BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P); + + BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G); + BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L); + + BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V); + BIND(B, B); BIND(N, N); BIND(M, M); + + BIND(FullStop, FullStop); + BIND(Comma, Comma); + BIND(ForwardSlash, ForwardSlash); + + BIND(Space, Space); BIND(Enter, Enter); + + BIND(LeftShift, LeftShift); + BIND(RightShift, RightShift); + BIND(LeftOption, Alt); + BIND(RightOption, Alt); + BIND(LeftControl, Control); + BIND(RightControl, Control); + + BIND(Left, Left); + BIND(Right, Right); + BIND(Up, Up); + BIND(Down, Down); + } +#undef BIND + + return MachineTypes::MappedKeyboardMachine::KeyNotMapped; +} diff --git a/Machines/Enterprise/Keyboard.hpp b/Machines/Enterprise/Keyboard.hpp new file mode 100644 index 000000000..0a13b7e1a --- /dev/null +++ b/Machines/Enterprise/Keyboard.hpp @@ -0,0 +1,61 @@ +// +// Keyboard.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Machines_Enterprise_Keyboard_hpp +#define Machines_Enterprise_Keyboard_hpp + +#include "../KeyboardMachine.hpp" + +namespace Enterprise { + +#define KeyCode(line, mask) (line << 8) | mask + +enum class Key: uint16_t { + N = 0x0000 | 0x01, Backslash = 0x0000 | 0x02, B = 0x0000 | 0x04, C = 0x0000 | 0x08, + V = 0x0000 | 0x10, X = 0x0000 | 0x20, Z = 0x0000 | 0x40, LeftShift = 0x0000 | 0x80, + + H = 0x0100 | 0x01, Lock = 0x0100 | 0x02, G = 0x0100 | 0x04, D = 0x0100 | 0x08, + F = 0x0100 | 0x10, S = 0x0100 | 0x20, A = 0x0100 | 0x40, Control = 0x0100 | 0x80, + + U = 0x0200 | 0x01, Q = 0x0200 | 0x02, Y = 0x0200 | 0x04, R = 0x0200 | 0x08, + T = 0x0200 | 0x10, E = 0x0200 | 0x20, W = 0x0200 | 0x40, Tab = 0x0200 | 0x80, + + k7 = 0x0300 | 0x01, k1 = 0x0300 | 0x02, k6 = 0x0300 | 0x04, k4 = 0x0300 | 0x08, + k5 = 0x0300 | 0x10, k3 = 0x0300 | 0x20, k2 = 0x0300 | 0x40, Escape = 0x0300 | 0x80, + + F4 = 0x0400 | 0x01, F8 = 0x0400 | 0x02, F3 = 0x0400 | 0x04, F6 = 0x0400 | 0x08, + F5 = 0x0400 | 0x10, F7 = 0x0400 | 0x20, F2 = 0x0400 | 0x40, F1 = 0x0400 | 0x80, + + k8 = 0x0500 | 0x01, k9 = 0x0500 | 0x04, Hyphen = 0x0500 | 0x08, + k0 = 0x0500 | 0x10, Tilde = 0x0500 | 0x20, Erase = 0x0500 | 0x40, + + J = 0x0600 | 0x01, K = 0x0600 | 0x04, Semicolon = 0x0600 | 0x08, + L = 0x0600 | 0x10, Colon = 0x0600 | 0x20, CloseSquareBracket = 0x0600 | 0x40, + + Stop = 0x0700 | 0x01, Down = 0x0700 | 0x02, Right = 0x0700 | 0x04, Up = 0x0700 | 0x08, + Hold = 0x0700 | 0x10, Left = 0x0700 | 0x20, Enter = 0x0700 | 0x40, Alt = 0x0700 | 0x80, + + M = 0x0800 | 0x01, Delete = 0x0800 | 0x02, Comma = 0x0800 | 0x04, + ForwardSlash = 0x0800 | 0x08, + FullStop = 0x0800 | 0x10, + RightShift = 0x0800 | 0x20, Space = 0x0800 | 0x40, Insert = 0x0800 | 0x80, + + I = 0x0900 | 0x01, O = 0x0900 | 0x04, At = 0x0900 | 0x08, + P = 0x0900 | 0x10, + OpenSquareBracket = 0x0900 | 0x20 +}; + +#undef KeyCode + +struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { + uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final; +}; + +} + +#endif /* Keyboard_hpp */ diff --git a/Machines/Enterprise/Nick.cpp b/Machines/Enterprise/Nick.cpp new file mode 100644 index 000000000..02628ead0 --- /dev/null +++ b/Machines/Enterprise/Nick.cpp @@ -0,0 +1,610 @@ +// +// Nick.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "Nick.hpp" + +#include + +namespace { + +uint16_t mapped_colour(uint8_t source) { + // On the Enterprise, red and green are 3-bit quantities; blue is a 2-bit quantity. + int red = ((source&0x01) << 2) | ((source&0x08) >> 2) | ((source&0x40) >> 6); + int green = ((source&0x02) << 1) | ((source&0x10) >> 3) | ((source&0x80) >> 7); + int blue = ((source&0x04) >> 1) | ((source&0x20) >> 5); + + assert(red <= 7); + assert(green <= 7); + assert(blue <= 3); + + red = (red << 1) + (red >> 3); + green = (green << 1) + (green >> 3); + blue = (blue << 2) + blue; + + assert(red <= 15); + assert(green <= 15); + assert(blue <= 15); + + // Duplicate bits where necessary to map to a full 4-bit range per channel. + const uint8_t parts[2] = { + uint8_t( + red + ), + uint8_t( + (green << 4) + blue + ) + }; + return *reinterpret_cast(parts); +} + +} + +using namespace Enterprise; + +Nick::Nick(const uint8_t *ram) : + crt_(57*16, 16, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4), + ram_(ram) { + + // Just use RGB for now. + crt_.set_display_type(Outputs::Display::DisplayType::RGB); + + // Crop to the centre 90% of the display. + crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f)); +} + +void Nick::write(uint16_t address, uint8_t value) { + switch(address & 3) { + case 0: + // Ignored: everything to do with external colour. + for(int c = 0; c < 8; c++) { + palette_[c + 8] = mapped_colour(uint8_t(((value & 0x1f) << 3) + c)); + } + break; + case 1: + if(output_type_ == OutputType::Border) { + set_output_type(OutputType::Border, true); + } + border_colour_ = mapped_colour(value); + break; + case 2: + line_parameter_base_ = uint16_t((line_parameter_base_ & 0xf000) | (value << 4)); + break; + case 3: + line_parameter_base_ = uint16_t((line_parameter_base_ & 0x0ff0) | (value << 12)); + + // Still a mystery to me: the exact meaning of the top two bits here. For now + // just treat a 0 -> 1 transition of the MSB as a forced frame restart. + if((value^line_parameter_control_) & value & 0x80) { + // For now: just force this to be the final line of this mode block. + // I'm unclear whether I should also reset the horizontal counter + // (i.e. completely abandon current video phase). + lines_remaining_ = 0xff; + should_reload_line_parameters_ = true; + } + line_parameter_control_ = value & 0xc0; + break; + } +} + +uint8_t Nick::read([[maybe_unused]] uint16_t address) { + return 0xff; +} + +Cycles Nick::get_time_until_z80_slot(Cycles after_period) const { + // Place Z80 accesses as the first six cycles in each sixteen-cycle window. + // That models video accesses as being the final ten. Which has the net effect + // of responding to the line parameter table interrupt flag as soon as it's + // loaded. + + // i.e. 0 -> 0, 1 -> 15 ... 15 -> 1. + return Cycles( + 15 ^ ((horizontal_counter_ + 15 + after_period.as()) & 15) + ); +} + +void Nick::run_for(Cycles duration) { + constexpr int line_length = 912; + + // TODO: test here for window < 57? Or maybe just nudge up left_/right_margin_ if they + // exactly equal 57? +#define add_window(x) \ + line_data_pointer_[0] += is_sync_or_pixels_ * line_data_per_column_increments_[0] * (x); \ + line_data_pointer_[1] += is_sync_or_pixels_ * line_data_per_column_increments_[1] * (x); \ + window += x; \ + if(window == left_margin_) is_sync_or_pixels_ = true; \ + if(window == right_margin_) is_sync_or_pixels_ = false; + + int clocks_remaining = duration.as(); + while(clocks_remaining) { + // Determine how many cycles are left this line. + const int clocks_this_line = std::min(clocks_remaining, line_length - horizontal_counter_); + + // Convert that into a [start/current] and end window. + int window = horizontal_counter_ >> 4; + const int end_window = (horizontal_counter_ + clocks_this_line) >> 4; + + // Advance the line counters. + clocks_remaining -= clocks_this_line; + horizontal_counter_ = (horizontal_counter_ + clocks_this_line) % line_length; + + // Do nothing if a window boundary isn't crossed. + if(window == end_window) continue; + + // HSYNC is signalled for four windows at the start of the line. + // I currently believe this happens regardless of Vsync mode. + // + // This is also when the non-palette line parameters + // are loaded, if appropriate. + if(!window) { + set_output_type(OutputType::Sync); + + // There's no increment to get to 0, it happens when the horizontal_counter_ + // is reset. So test for active bit effect manually. + if(!left_margin_) is_sync_or_pixels_ = true; + if(!right_margin_) is_sync_or_pixels_ = false; + } + + while(window < 4 && window < end_window) { + if(should_reload_line_parameters_) { + switch(window) { + // First slot: line count, mode and interrupt flag. + case 0: + // Byte 0: lines remaining. + lines_remaining_ = ram_[line_parameter_pointer_]; + + // Set the new interrupt line output. + interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80; + + // Determine the mode and depth, and hence the column size. + mode_ = Mode((ram_[line_parameter_pointer_ + 1] >> 1)&7); + bpp_ = 1 << ((ram_[line_parameter_pointer_ + 1] >> 5)&3); + switch(mode_) { + default: + case Mode::Pixel: + column_size_ = 16 / bpp_; + line_data_per_column_increments_[0] = 2; + line_data_per_column_increments_[1] = 0; + break; + + case Mode::LPixel: + case Mode::CH64: + case Mode::CH128: + case Mode::CH256: + column_size_ = 8 / bpp_; + line_data_per_column_increments_[0] = 1; + line_data_per_column_increments_[1] = 0; + break; + + case Mode::Attr: + column_size_ = 8; + line_data_per_column_increments_[0] = 1; + line_data_per_column_increments_[1] = 1; + break; + } + + vres_ = ram_[line_parameter_pointer_ + 1] & 0x10; + reload_line_parameter_pointer_ = ram_[line_parameter_pointer_ + 1] & 0x01; + break; + + // Second slot: margins and ALT/IND bits. + case 1: + // Determine the margins. + left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f; + right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f; + + // Set up the alternative palettes, + switch(mode_) { + default: + break; + + // NB: LSBALT/MSBALT and ALTIND0/ALTIND1 appear to have opposite effects on palette selection. + + case Mode::Pixel: + case Mode::LPixel: { + const uint8_t flags = ram_[line_parameter_pointer_ + 2]; + + // Use MSBALT and LSBALT to pick the alt_ind_palettes. + // + // LSBALT = b6 of params[2], if set => character codes with bit 6 set should use palette indices 4... instead of 0... . + // MSBALT = b7 of params[2], if set => character codes with bit 7 set should use palette indices 2 and 3. + two_colour_mask_ = 0xff &~ (((flags&0x80) >> 7) | ((flags&0x40) << 1)); + + alt_ind_palettes[0] = palette_; + alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x80) ? 2 : 0); + + alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x40) ? 4 : 0); + alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x40) ? 4 : 0); + } break; + + case Mode::CH64: + case Mode::CH128: + case Mode::CH256: { + const uint8_t flags = ram_[line_parameter_pointer_ + 3]; + + // Use ALTIND0 and ALTIND1 to pick the alt_ind_palettes. + // + // ALTIND1 = b6 of params[3], if set => character codes with bit 7 set should use palette indices 2 and 3. + // ALTIND0 = b7 of params[3], if set => character codes with bit 6 set should use palette indices 4... instead of 0... . + alt_ind_palettes[0] = palette_; + alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x40) ? 2 : 0); + + alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x80) ? 4 : 0); + alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x80) ? 4 : 0); + } break; + } + break; + + // Third slot: Line data pointer 1. + case 2: + start_line_data_pointer_[0] = ram_[line_parameter_pointer_ + 4]; + start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8; + + line_data_pointer_[0] = start_line_data_pointer_[0]; + break; + + // Fourth slot: Line data pointer 2. + case 3: + start_line_data_pointer_[1] = ram_[line_parameter_pointer_ + 6]; + start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8; + + line_data_pointer_[1] = start_line_data_pointer_[1]; + break; + } + } + + ++output_duration_; + add_window(1); + } + if(window == 4) { + if(mode_ == Mode::Vsync) { + set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank); + } else { + set_output_type(OutputType::Blank); + } + } + + // Deal with vsync mode out here. + if(mode_ == Mode::Vsync) { + if(window >= 4) { + while(window < end_window) { + // Skip straight to the next event. + int next_event = end_window; + if(window < left_margin_) next_event = std::min(next_event, left_margin_); + if(window < right_margin_) next_event = std::min(next_event, right_margin_); + + output_duration_ += next_event - window; + add_window(next_event - window); + set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank); + } + } + } else { + // If present then the colour burst is output for the period from + // the start of window 6 to the end of window 10. + // + // The first 8 palette entries also need to be fetched here. + while(window < 10 && window < end_window) { + if(window == 6) { + set_output_type(OutputType::ColourBurst); + } + + if(should_reload_line_parameters_ && window < 8) { + const int base = (window - 4) << 1; + assert(base < 7); + palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]); + palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]); + } + + ++output_duration_; + add_window(1); + } + + if(window >= 10) { + if(window == 10) { + set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border); + } + + while(window < end_window) { + int next_event = end_window; + if(window < left_margin_) next_event = std::min(next_event, left_margin_); + if(window < right_margin_) next_event = std::min(next_event, right_margin_); + + if(is_sync_or_pixels_) { + +#define DispatchBpp(func) \ + switch(bpp_) { \ + default: \ + case 1: func(1)(pixel_pointer_, output_duration); break; \ + case 2: func(2)(pixel_pointer_, output_duration); break; \ + case 4: func(4)(pixel_pointer_, output_duration); break; \ + case 8: func(8)(pixel_pointer_, output_duration); break; \ + } + +#define pixel(x) output_pixel +#define lpixel(x) output_pixel +#define ch256(x) output_character +#define ch128(x) output_character +#define ch64(x) output_character +#define attr(x) output_attributed + + int columns_remaining = next_event - window; + while(columns_remaining) { + if(!pixel_pointer_) { + if(output_duration_) { + set_output_type(OutputType::Pixels, true); + } + pixel_pointer_ = allocated_pointer_ = reinterpret_cast(crt_.begin_data(allocation_size)); + } + + if(allocated_pointer_) { + const int output_duration = std::min(columns_remaining, int(allocated_pointer_ + allocation_size - pixel_pointer_) / column_size_); + + switch(mode_) { + default: + case Mode::Pixel: DispatchBpp(pixel); break; + case Mode::LPixel: DispatchBpp(lpixel); break; + case Mode::CH256: DispatchBpp(ch256); break; + case Mode::CH128: DispatchBpp(ch128); break; + case Mode::CH64: DispatchBpp(ch64); break; + case Mode::Attr: DispatchBpp(attr); break; + } + + pixel_pointer_ += output_duration * column_size_; + output_duration_ += output_duration; + if(pixel_pointer_ - allocated_pointer_ == allocation_size) { + set_output_type(OutputType::Pixels, true); + } + columns_remaining -= output_duration; + add_window(output_duration); + } else { + output_duration_ += columns_remaining; + add_window(columns_remaining); + columns_remaining = 0; + } + } + +#undef attr +#undef ch64 +#undef ch128 +#undef ch256 +#undef pixel +#undef lpixel +#undef DispatchBpp + } else { + output_duration_ += next_event - window; + add_window(next_event - window); + } + + set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border); + } + } + } + + // Check for end of line. + if(!horizontal_counter_) { + assert(window == 57); + + ++lines_remaining_; + if(!lines_remaining_) { + should_reload_line_parameters_ = true; + + // Check for end-of-frame. + if(reload_line_parameter_pointer_) { + line_parameter_pointer_ = line_parameter_base_; + } else { + line_parameter_pointer_ += 16; + } + } else { + should_reload_line_parameters_ = false; + } + + // Deal with VRES and other address reloading, dependant upon mode. + switch(mode_) { + default: break; + case Mode::CH64: + case Mode::CH128: + case Mode::CH256: + line_data_pointer_[0] = start_line_data_pointer_[0]; + ++line_data_pointer_[1]; + break; + + case Mode::Pixel: + case Mode::LPixel: + case Mode::Attr: + // Reload the pixel or attribute address if VRES is clear. + if(!vres_) { + line_data_pointer_[0] = start_line_data_pointer_[0]; + } + break; + } + } + } + +#undef add_window + +} + +void Nick::set_output_type(OutputType type, bool force_flush) { + if(type == output_type_ && !force_flush) { + return; + } + if(output_duration_) { + switch(output_type_) { + case OutputType::Border: { + uint16_t *const colour_pointer = reinterpret_cast(crt_.begin_data(1)); + if(colour_pointer) *colour_pointer = border_colour_; + crt_.output_level(output_duration_*16); + } break; + + case OutputType::Pixels: { + crt_.output_data(output_duration_*16, size_t(output_duration_*column_size_)); + pixel_pointer_ = nullptr; + allocated_pointer_ = nullptr; + } break; + + case OutputType::Sync: crt_.output_sync(output_duration_*16); break; + case OutputType::Blank: crt_.output_blank(output_duration_*16); break; + case OutputType::ColourBurst: crt_.output_colour_burst(output_duration_*16, 0); break; + } + } + + output_duration_ = 0; + output_type_ = type; +} + +// MARK: - Sequence points. + +Cycles Nick::get_next_sequence_point() const { + // TODO: the below is incorrect; unit test and correct. + // Changing to e.g. Cycles(1) reveals the relevant discrepancy. +// return Cycles(1); + + constexpr int load_point = 2*16; + + // Any mode line may cause a change in the interrupt output, so as a first blush + // just always report the time until the end of the mode line. + if(lines_remaining_ || horizontal_counter_ >= load_point) { + return Cycles(load_point + (912 - horizontal_counter_) + (0xff - lines_remaining_) * 912); + } else { + return Cycles(load_point - horizontal_counter_); + } +} + +// MARK: - CRT passthroughs. + +void Nick::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); +} + +Outputs::Display::ScanStatus Nick::get_scaled_scan_status() const { + return crt_.get_scaled_scan_status(); +} + +// MARK: - Specific pixel outputters. + +#define output1bpp(x) \ + target[0] = palette[(x & 0x80) >> 7]; \ + target[1] = palette[(x & 0x40) >> 6]; \ + target[2] = palette[(x & 0x20) >> 5]; \ + target[3] = palette[(x & 0x10) >> 4]; \ + target[4] = palette[(x & 0x08) >> 3]; \ + target[5] = palette[(x & 0x04) >> 2]; \ + target[6] = palette[(x & 0x02) >> 1]; \ + target[7] = palette[(x & 0x01) >> 0]; \ + target += 8 + +#define output2bpp(x) \ + target[0] = palette_[((x & 0x80) >> 7) | ((x & 0x08) >> 2)]; \ + target[1] = palette_[((x & 0x40) >> 6) | ((x & 0x04) >> 1)]; \ + target[2] = palette_[((x & 0x20) >> 5) | ((x & 0x02) >> 0)]; \ + target[3] = palette_[((x & 0x10) >> 4) | ((x & 0x01) << 1)]; \ + target += 4 + +#define output4bpp(x) \ + target[0] = palette_[((x & 0x02) << 2) | ((x & 0x20) >> 3) | ((x & 0x08) >> 2) | ((x & 0x80) >> 7)]; \ + target[1] = palette_[((x & 0x01) << 3) | ((x & 0x10) >> 2) | ((x & 0x04) >> 1) | ((x & 0x40) >> 6)]; \ + target += 2 + +#define output8bpp(x) \ + target[0] = mapped_colour(x); \ + ++target + +template void Nick::output_pixel(uint16_t *target, int columns) const { + static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8); + + int index = 0; + for(int c = 0; c < columns; c++) { + uint8_t pixels[2] = { + ram_[(line_data_pointer_[0] + index) & 0xffff], + ram_[(line_data_pointer_[0] + index + 1) & 0xffff] + }; + index += is_lpixel ? 1 : 2; + + switch(bpp) { + default: + case 1: { + const uint16_t *palette = alt_ind_palettes[((pixels[0] >> 6) & 0x02) | (pixels[0]&1)]; + pixels[0] &= two_colour_mask_; + output1bpp(pixels[0]); + + if constexpr (!is_lpixel) { + palette = alt_ind_palettes[((pixels[1] >> 6) & 0x02) | (pixels[1]&1)]; + pixels[1] &= two_colour_mask_; + output1bpp(pixels[1]); + } + } break; + + case 2: + output2bpp(pixels[0]); + if constexpr (!is_lpixel) { + output2bpp(pixels[1]); + } + break; + + case 4: + output4bpp(pixels[0]); + if constexpr (!is_lpixel) { + output4bpp(pixels[1]); + } + break; + + case 8: + output8bpp(pixels[0]); + if constexpr (!is_lpixel) { + output8bpp(pixels[1]); + } + break; + } + } +} + +template void Nick::output_character(uint16_t *target, int columns) const { + static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8); + + for(int c = 0; c < columns; c++) { + const uint8_t character = ram_[(line_data_pointer_[0] + c) & 0xffff]; + const uint8_t pixels = ram_[( + (line_data_pointer_[1] << index_bits) + + (character & ((1 << index_bits) - 1)) + ) & 0xffff]; + + switch(bpp) { + default: + assert(false); + break; + + case 1: { + // This applies ALTIND0 and ALTIND1. + const uint16_t *palette = alt_ind_palettes[character >> 6]; + output1bpp(pixels); + } break; + + case 2: output2bpp(pixels); break; + case 4: output4bpp(pixels); break; + case 8: output8bpp(pixels); break; + } + } +} + +template void Nick::output_attributed(uint16_t *target, int columns) const { + static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8); + + for(int c = 0; c < columns; c++) { + const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff]; + const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff]; + + const uint16_t palette[2] = { + palette_[attributes >> 4], palette_[attributes & 0x0f] + }; + output1bpp(pixels); + } +} + +#undef output1bpp +#undef output2bpp +#undef output4bpp +#undef output8bpp diff --git a/Machines/Enterprise/Nick.hpp b/Machines/Enterprise/Nick.hpp new file mode 100644 index 000000000..b9ce44439 --- /dev/null +++ b/Machines/Enterprise/Nick.hpp @@ -0,0 +1,108 @@ +// +// Nick.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/06/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Nick_hpp +#define Nick_hpp + +#include +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Outputs/CRT/CRT.hpp" + +namespace Enterprise { + +class Nick { + public: + Nick(const uint8_t *ram); + + void write(uint16_t address, uint8_t value); + uint8_t read(uint16_t address); + + void run_for(Cycles); + Cycles get_time_until_z80_slot(Cycles after_period) const; + + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + Outputs::Display::ScanStatus get_scaled_scan_status() const; + + Cycles get_next_sequence_point() const; + + /*! + @returns The current state of the interrupt line — @c true for active; + @c false for inactive. + */ + inline bool get_interrupt_line() const { + return interrupt_line_; + } + + private: + Outputs::CRT::CRT crt_; + const uint8_t *const ram_; + + // CPU-provided state. + uint8_t line_parameter_control_ = 0xc0; + uint16_t line_parameter_base_ = 0x0000; + uint16_t border_colour_ = 0; + + // Ephemerals, related to current video position. + int horizontal_counter_ = 0; + uint16_t line_parameter_pointer_ = 0x0000; + bool should_reload_line_parameters_ = true; + uint16_t line_data_pointer_[2]; + uint16_t start_line_data_pointer_[2]; + + // Current mode line parameters. + uint8_t lines_remaining_ = 0x00; + uint8_t two_colour_mask_ = 0xff; + int left_margin_ = 0, right_margin_ = 0; + const uint16_t *alt_ind_palettes[4]; + enum class Mode { + Vsync, + Pixel, + Attr, + CH256, + CH128, + CH64, + Unused, + LPixel, + } mode_ = Mode::Vsync; + bool is_sync_or_pixels_ = false; + int bpp_ = 0; + int column_size_ = 0; + bool interrupt_line_ = true; + int line_data_per_column_increments_[2] = {0, 0}; + bool vres_ = false; + bool reload_line_parameter_pointer_ = false; + + // An accumulator for border output regions. + int border_duration_ = 0; + + // The destination for new pixels. + static constexpr int allocation_size = 336; + static_assert((allocation_size % 16) == 0, "Allocation size must be a multiple of 16"); + uint16_t *pixel_pointer_ = nullptr, *allocated_pointer_ = nullptr; + + // Output transitions. + enum class OutputType { + Sync, Blank, Pixels, Border, ColourBurst + }; + void set_output_type(OutputType, bool force_flush = false); + int output_duration_ = 0; + OutputType output_type_ = OutputType::Sync; + + // Current palette. + uint16_t palette_[16]{}; + + // Specific outputters. + template void output_pixel(uint16_t *target, int columns) const; + template void output_character(uint16_t *target, int columns) const; + template void output_attributed(uint16_t *target, int columns) const; +}; + + +} + +#endif /* Nick_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 0a576e867..0c1e92500 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -20,6 +20,7 @@ #include "../ColecoVision/ColecoVision.hpp" #include "../Commodore/Vic-20/Vic20.hpp" #include "../Electron/Electron.hpp" +#include "../Enterprise/Enterprise.hpp" #include "../MasterSystem/MasterSystem.hpp" #include "../MSX/MSX.hpp" #include "../Oric/Oric.hpp" @@ -34,6 +35,7 @@ #include "../../Analyser/Static/Atari2600/Target.hpp" #include "../../Analyser/Static/AtariST/Target.hpp" #include "../../Analyser/Static/Commodore/Target.hpp" +#include "../../Analyser/Static/Enterprise/Target.hpp" #include "../../Analyser/Static/Macintosh/Target.hpp" #include "../../Analyser/Static/MSX/Target.hpp" #include "../../Analyser/Static/Oric/Target.hpp" @@ -49,7 +51,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe Machine::DynamicMachine *machine = nullptr; try { -#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine(name::Machine::m(target, rom_fetcher)); break; +#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<::name::Machine>(name::Machine::m(target, rom_fetcher)); break; #define Bind(m) BindD(m, m) switch(target->machine) { Bind(AmstradCPC) @@ -61,6 +63,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe BindD(Coleco::Vision, ColecoVision) BindD(Commodore::Vic20, Vic20) Bind(Electron) + Bind(Enterprise) Bind(MSX) Bind(Oric) BindD(Sega::MasterSystem, MasterSystem) @@ -127,6 +130,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) case Analyser::Machine::AtariST: return "AtariST"; case Analyser::Machine::ColecoVision: return "ColecoVision"; case Analyser::Machine::Electron: return "Electron"; + case Analyser::Machine::Enterprise: return "Enterprise"; case Analyser::Machine::Macintosh: return "Macintosh"; case Analyser::Machine::MasterSystem: return "MasterSystem"; case Analyser::Machine::MSX: return "MSX"; @@ -148,6 +152,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { case Analyser::Machine::AtariST: return "Atari ST"; case Analyser::Machine::ColecoVision: return "ColecoVision"; case Analyser::Machine::Electron: return "Acorn Electron"; + case Analyser::Machine::Enterprise: return "Enterprise"; case Analyser::Machine::Macintosh: return "Apple Macintosh"; case Analyser::Machine::MasterSystem: return "Sega Master System"; case Analyser::Machine::MSX: return "MSX"; @@ -177,6 +182,7 @@ std::vector Machine::AllMachines(Type type, bool long_names) { AddName(AppleIIgs); AddName(AtariST); AddName(Electron); + AddName(Enterprise); AddName(Macintosh); AddName(MSX); AddName(Oric); @@ -226,6 +232,7 @@ std::map> Machine::Target Add(AppleIIgs); Add(AtariST); AddMapped(Electron, Acorn); + Add(Enterprise); Add(Macintosh); Add(MSX); Add(Oric); diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index 7a12069ff..99b2634bd 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -27,8 +27,15 @@ Request::Request(Name name, bool optional) { } Request Request::append(Node::Type type, const Request &rhs) { - // Start with the easiest case: this is already an appropriate - // request, and so is the new thing. + // If either side is empty, act appropriately. + if(node.empty() && !rhs.node.empty()) { + return rhs; + } + if(rhs.node.empty()) { + return *this; + } + + // Just copy in the RHS child nodes if types match. if(node.type == type && rhs.node.type == type) { Request new_request = *this; new_request.node.children.insert(new_request.node.children.end(), rhs.node.children.begin(), rhs.node.children.end()); @@ -404,6 +411,62 @@ Description::Description(Name name) { *this = Description(name, "DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620u); break; + case Name::EnterpriseEXOS10: { + const std::initializer_list filenames = {"exos10.bin", "Exos (198x)(Enterprise).bin"}; + *this = Description(name, "Enterprise", "the Enterprise EXOS ROM v1.0", filenames, 32 * 1024, 0x30b26387u); + } break; + case Name::EnterpriseEXOS20: { + const std::initializer_list filenames = {"exos20.bin", "Expandible OS v2.0 (1984)(Intelligent Software).bin"}; + *this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.0", filenames, 32 * 1024, 0xd421795fu); + } break; + case Name::EnterpriseEXOS21: { + const std::initializer_list filenames = {"exos21.bin", "Expandible OS v2.1 (1985)(Intelligent Software).bin"}; + *this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 32 * 1024, 0x982a3b44u); + } break; + case Name::EnterpriseEXOS23: { + const std::initializer_list filenames = {"exos23.bin", "Expandible OS v2.3 (1987)(Intelligent Software).bin"}; + *this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 64 * 1024, 0x24838410u); + } break; + + case Name::EnterpriseBASIC10: { + const std::initializer_list filenames = {"basic10.bin"}; + *this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0", filenames, 16 * 1024, 0xd62e4fb7u); + } break; + case Name::EnterpriseBASIC10Part1: { + const std::initializer_list filenames = {"BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin"}; + *this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 1", filenames, 8193, 0x37bf48e1u); + } break; + case Name::EnterpriseBASIC10Part2: { + const std::initializer_list filenames = {"BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin"}; + *this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 2", filenames, 8193, 0xc5298c79u); + } break; + case Name::EnterpriseBASIC11: { + const std::initializer_list filenames = {"basic11.bin"}; + *this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1", filenames, 16 * 1024, 0x683cf455u); + } break; + case Name::EnterpriseBASIC11Suffixed: { + const std::initializer_list filenames = {"BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin"}; + *this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1, with trailing byte", filenames, 16385, 0xc96b7602u); + } break; + case Name::EnterpriseBASIC21: { + const std::initializer_list filenames = { + "basic21.bin", + "BASIC Interpreter v2.1 (1985)(Intelligent Software).bin", + "BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin" + }; + const std::initializer_list crcs = { 0x55f96251, 0x683cf455 }; + *this = Description(name, "Enterprise", "the Enterprise BASIC ROM v2.1", filenames, 16 * 1024, crcs); + } break; + + case Name::EnterpriseEPDOS: { + const std::initializer_list filenames = {"epdos.bin", "EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"}; + *this = Description(name, "Enterprise", "the Enterprise EPDOS ROM", filenames, 32 * 1024, 0x201319ebu); + } break; + case Name::EnterpriseEXDOS: { + const std::initializer_list filenames = {"exdos.bin", "EX-DOS EPROM (198x)(Enterprise).bin"}; + *this = Description(name, "Enterprise", "the Enterprise EXDOS ROM", filenames, 16 * 1024, 0xe6daa0e9u); + } break; + case Name::Macintosh128k: *this = Description(name, "Macintosh", "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28u); break; case Name::Macintosh512k: *this = Description(name, "Macintosh", "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); break; case Name::MacintoshPlus: { diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index 88ec7ca5b..4f481f2e2 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -71,6 +71,22 @@ enum Name { DiskIIStateMachine13Sector, DiskIIBoot13Sector, + // Enterprise. + EnterpriseEXOS10, + EnterpriseEXOS20, + EnterpriseEXOS21, + EnterpriseEXOS23, + + EnterpriseBASIC10, + EnterpriseBASIC10Part1, + EnterpriseBASIC10Part2, + EnterpriseBASIC11, + EnterpriseBASIC11Suffixed, + EnterpriseBASIC21, + + EnterpriseEPDOS, + EnterpriseEXDOS, + // Macintosh. Macintosh128k, Macintosh512k, @@ -237,6 +253,10 @@ struct Request { bool is_optional = false; std::vector children; + bool empty() const { + return type == Type::One && name == Name::None; + } + void add_descriptions(std::vector &) const; bool validate(Map &) const; void visit( diff --git a/Numeric/LFSR.hpp b/Numeric/LFSR.hpp index 3bf8a8881..b45eba802 100644 --- a/Numeric/LFSR.hpp +++ b/Numeric/LFSR.hpp @@ -12,6 +12,8 @@ #include #include +#include "Sizes.hpp" + namespace Numeric { template struct LSFRPolynomial {}; @@ -67,8 +69,8 @@ template > 1) ^ (result * polynomial); + const auto result = IntType(value_ & 1); + value_ = IntType((value_ >> 1) ^ (result * polynomial)); return result; } @@ -76,6 +78,8 @@ template class LFSRv: public LFSR::type, polynomial> {}; + } #endif /* LFSR_h */ diff --git a/InstructionSets/Sizes.hpp b/Numeric/Sizes.hpp similarity index 96% rename from InstructionSets/Sizes.hpp rename to Numeric/Sizes.hpp index 9d3ed9522..d9a56a40a 100644 --- a/InstructionSets/Sizes.hpp +++ b/Numeric/Sizes.hpp @@ -10,6 +10,7 @@ #define Sizes_h #include +#include /*! Maps to the smallest integral type that can contain max_value, from the following options: diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5bdace657..8587d4e08 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -17,6 +17,17 @@ 4B051C93266D9D6900CA44E8 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; 4B051C95266EF50200CA44E8 /* AppleIIOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */; }; 4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C96266EF5F600CA44E8 /* CSAppleII.mm */; }; + 4B051CA22676F52200CA44E8 /* Enterprise.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA12676F52200CA44E8 /* Enterprise.cpp */; }; + 4B051CA32676F52200CA44E8 /* Enterprise.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA12676F52200CA44E8 /* Enterprise.cpp */; }; + 4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */; }; + 4B051CA926781D6500CA44E8 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */; }; + 4B051CAC26783E2000CA44E8 /* Nick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAA26783E2000CA44E8 /* Nick.cpp */; }; + 4B051CAD26783E2000CA44E8 /* Nick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAA26783E2000CA44E8 /* Nick.cpp */; }; + 4B051CB0267C1CA200CA44E8 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */; }; + 4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */; }; + 4B051CB3267D3FF800CA44E8 /* EnterpriseNickTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */; }; + 4B051CB62680158600CA44E8 /* EXDos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CB42680158600CA44E8 /* EXDos.cpp */; }; + 4B051CB72680158600CA44E8 /* EXDos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051CB42680158600CA44E8 /* EXDos.cpp */; }; 4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; }; 4B05401F219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; }; 4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; }; @@ -312,7 +323,7 @@ 4B778F0023A5EB990000D260 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518931F75FD1B00926311 /* G64.cpp */; }; 4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */; }; 4B778F0223A5EBA40000D260 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; }; - 4B778F0323A5EBB00000D260 /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; }; + 4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */; }; 4B778F0423A5EBB00000D260 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; }; 4B778F0523A5EBB00000D260 /* ST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE0A3EC237BB170002AB46F /* ST.cpp */; }; 4B778F0623A5EC150000D260 /* CAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04E81FC9E5DA00F43484 /* CAS.cpp */; }; @@ -950,8 +961,8 @@ 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 */; }; - 4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; }; - 4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; }; + 4BEBFB4D2002C4BF000708CC /* FAT12.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */; }; + 4BEBFB4E2002C4BF000708CC /* FAT12.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */; }; 4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; }; 4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; }; 4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; }; @@ -987,6 +998,8 @@ 4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; 4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */; }; 4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; }; + 4BFEA2EF2682A7B900EBF94C /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; }; + 4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */; }; 4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; }; 4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; }; 4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */; }; @@ -1028,6 +1041,18 @@ 4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIOptionsPanel.swift; sourceTree = ""; }; 4B051C96266EF5F600CA44E8 /* CSAppleII.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAppleII.mm; sourceTree = ""; }; 4B051C98266EF60500CA44E8 /* CSAppleII.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSAppleII.h; sourceTree = ""; }; + 4B051CA02676F52200CA44E8 /* Enterprise.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Enterprise.hpp; sourceTree = ""; }; + 4B051CA12676F52200CA44E8 /* Enterprise.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Enterprise.cpp; sourceTree = ""; }; + 4B051CA526781D6500CA44E8 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; + 4B051CA626781D6500CA44E8 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4B051CAA26783E2000CA44E8 /* Nick.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Nick.cpp; sourceTree = ""; }; + 4B051CAB26783E2000CA44E8 /* Nick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Nick.hpp; sourceTree = ""; }; + 4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; + 4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; + 4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseNickTests.mm; sourceTree = ""; }; + 4B051CB42680158600CA44E8 /* EXDos.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EXDos.cpp; sourceTree = ""; }; + 4B051CB52680158600CA44E8 /* EXDos.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EXDos.hpp; sourceTree = ""; }; 4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = ""; }; 4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; }; 4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; }; @@ -1998,8 +2023,8 @@ 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = ""; }; 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SoundGenerator.cpp; sourceTree = ""; }; 4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundGenerator.hpp; sourceTree = ""; }; - 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSXDSK.cpp; sourceTree = ""; }; - 4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = ""; }; + 4BEBFB4B2002C4BF000708CC /* FAT12.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FAT12.cpp; sourceTree = ""; }; + 4BEBFB4C2002C4BF000708CC /* FAT12.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FAT12.hpp; sourceTree = ""; }; 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskROM.cpp; sourceTree = ""; }; 4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskROM.hpp; sourceTree = ""; }; 4BEDA3B425B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = ""; }; @@ -2049,6 +2074,9 @@ 4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImplicitSectors.hpp; sourceTree = ""; }; 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImplicitSectors.cpp; sourceTree = ""; }; 4BFE7B861FC39BF100160B38 /* StandardOptions.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StandardOptions.hpp; sourceTree = ""; }; + 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Dave.cpp; sourceTree = ""; }; + 4BFEA2EE2682A7B900EBF94C /* Dave.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Dave.hpp; sourceTree = ""; }; + 4BFEA2F12682A90200EBF94C /* Sizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Sizes.hpp; sourceTree = ""; }; 4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; 4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = ""; }; @@ -2090,6 +2118,33 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4B051C9F2676F52200CA44E8 /* Enterprise */ = { + isa = PBXGroup; + children = ( + 4BFEA2ED2682A7B900EBF94C /* Dave.cpp */, + 4B051CA12676F52200CA44E8 /* Enterprise.cpp */, + 4B051CB42680158600CA44E8 /* EXDos.cpp */, + 4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */, + 4B051CAA26783E2000CA44E8 /* Nick.cpp */, + 4BFEA2EE2682A7B900EBF94C /* Dave.hpp */, + 4B051CA02676F52200CA44E8 /* Enterprise.hpp */, + 4B051CB52680158600CA44E8 /* EXDos.hpp */, + 4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */, + 4B051CAB26783E2000CA44E8 /* Nick.hpp */, + ); + path = Enterprise; + sourceTree = ""; + }; + 4B051CA426781D6500CA44E8 /* Enterprise */ = { + isa = PBXGroup; + children = ( + 4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */, + 4B051CA526781D6500CA44E8 /* StaticAnalyser.hpp */, + 4B051CA626781D6500CA44E8 /* Target.hpp */, + ); + path = Enterprise; + sourceTree = ""; + }; 4B055A761FAE78210060FFFF /* Frameworks */ = { isa = PBXGroup; children = ( @@ -2655,34 +2710,36 @@ 4B45188C1F75FD1B00926311 /* Formats */ = { isa = PBXGroup; children = ( + 4B80CD74256CA15E00176FCC /* 2MG.cpp */, 4B45188D1F75FD1B00926311 /* AcornADF.cpp */, 4B0333AD2094081A0050B93D /* AppleDSK.cpp */, 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */, 4B4518911F75FD1B00926311 /* D64.cpp */, 4BAF2B4C2004580C00480230 /* DMK.cpp */, + 4BEBFB4B2002C4BF000708CC /* FAT12.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 */, 4B4518991F75FD1B00926311 /* SSD.cpp */, 4BE0A3EC237BB170002AB46F /* ST.cpp */, 4B7BA03323C58B1E00B98D9E /* STX.cpp */, 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */, + 4B80CD75256CA15E00176FCC /* 2MG.hpp */, 4B45188E1F75FD1B00926311 /* AcornADF.hpp */, 4B0333AE2094081A0050B93D /* AppleDSK.hpp */, 4B4518901F75FD1B00926311 /* CPCDSK.hpp */, 4B4518921F75FD1B00926311 /* D64.hpp */, 4BAF2B4D2004580C00480230 /* DMK.hpp */, + 4BEBFB4C2002C4BF000708CC /* FAT12.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 */, 4B45189A1F75FD1B00926311 /* SSD.hpp */, @@ -2690,8 +2747,6 @@ 4B7BA03223C58B1E00B98D9E /* STX.hpp */, 4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */, 4BFDD7891F7F2DB4008579B9 /* Utility */, - 4B80CD74256CA15E00176FCC /* 2MG.cpp */, - 4B80CD75256CA15E00176FCC /* 2MG.hpp */, ); path = Formats; sourceTree = ""; @@ -3048,6 +3103,7 @@ children = ( 4B7BA03E23D55E7900B98D9E /* CRC.hpp */, 4B7BA03F23D55E7900B98D9E /* LFSR.hpp */, + 4BFEA2F12682A90200EBF94C /* Sizes.hpp */, ); name = Numeric; path = ../../Numeric; @@ -3185,6 +3241,7 @@ 4B8944FB201967B4007DE474 /* Commodore */, 4B894507201967B4007DE474 /* Disassembler */, 4BD67DC8209BE4D600AB2146 /* DiskII */, + 4B051CA426781D6500CA44E8 /* Enterprise */, 4BB4BFB622A4372E0069048D /* Macintosh */, 4B89450F201967B4007DE474 /* MSX */, 4B8944F6201967B4007DE474 /* Oric */, @@ -3990,12 +4047,8 @@ 4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = { isa = PBXGroup; children = ( - 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */, - 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */, - 4BC751B11D157E61006C31D9 /* 6522Tests.swift */, - 4B1E85801D176468001EF87D /* 6532Tests.swift */, - 4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */, - 4B8DF5132550D62900F3433C /* 65816kromTests.swift */, + 4B85322922778E4200F26553 /* Comparative68000.hpp */, + 4B90467222C6FA31000E2074 /* TestRunner68000.hpp */, 4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */, 4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */, 4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */, @@ -4004,43 +4057,48 @@ 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */, 4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */, 4BD388872239E198002D14B5 /* 68000Tests.mm */, - 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */, 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */, - 4B049CDC1DA3C82F00322067 /* BCDTest.swift */, - 4B3BA0C41D318B44005DD7A7 /* Bridges */, - 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */, - 4B85322922778E4200F26553 /* Comparative68000.hpp */, 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, - 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */, 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */, - 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */, + 4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */, 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */, - 4BB73EB81B587A5100552FC2 /* Info.plist */, - 4B4F477B253530B7004245B8 /* Jeek816Tests.swift */, - 4B1414611B58888700E04248 /* KlausDormannTests.swift */, 4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */, 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */, 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */, 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */, 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */, - 4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, 4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */, 4BE76CF822641ED300ACD6FA /* QLTests.mm */, 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */, - 4B1414631B588A1100E04248 /* Test Binaries */, - 4B90467222C6FA31000E2074 /* TestRunner68000.hpp */, 4B2AF8681E513FC20027EE29 /* TIATests.mm */, 4B1D08051E0F7A1100763741 /* TimeTests.mm */, - 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */, 4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */, 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */, + 4BB73EB81B587A5100552FC2 /* Info.plist */, + 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */, + 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */, + 4BC751B11D157E61006C31D9 /* 6522Tests.swift */, + 4B1E85801D176468001EF87D /* 6532Tests.swift */, + 4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */, + 4B8DF5132550D62900F3433C /* 65816kromTests.swift */, + 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */, + 4B049CDC1DA3C82F00322067 /* BCDTest.swift */, + 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */, + 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */, + 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */, + 4B4F477B253530B7004245B8 /* Jeek816Tests.swift */, + 4B1414611B58888700E04248 /* KlausDormannTests.swift */, + 4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */, + 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */, 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */, 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */, 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */, 4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */, + 4B3BA0C41D318B44005DD7A7 /* Bridges */, + 4B1414631B588A1100E04248 /* Test Binaries */, ); path = "Clock SignalTests"; sourceTree = ""; @@ -4075,6 +4133,7 @@ 4B7A90E22041097C008514A2 /* ColecoVision */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4B051C9F2676F52200CA44E8 /* Enterprise */, 4B7F188B2154825D00388727 /* MasterSystem */, 4B79A4FC1FC8FF9800EEDAD5 /* MSX */, 4BCF1FA51DADC3E10039D2E7 /* Oric */, @@ -5177,6 +5236,7 @@ 4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */, 4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */, 4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */, + 4B051CA926781D6500CA44E8 /* StaticAnalyser.cpp in Sources */, 4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */, 4B80CD77256CA16600176FCC /* 2MG.cpp in Sources */, 4B0F1BE62602FF9D00B85C66 /* ZX8081.cpp in Sources */, @@ -5186,6 +5246,7 @@ 4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */, 4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */, 4B0ACC2B23775819008902D0 /* Video.cpp in Sources */, + 4B051CA32676F52200CA44E8 /* Enterprise.cpp in Sources */, 4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */, 4B12C0EE1FCFAD1A005BFD93 /* Keyboard.cpp in Sources */, 4BCD634A22D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */, @@ -5214,6 +5275,7 @@ 4BB307BC235001C300457D33 /* 6850.cpp in Sources */, 4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, 4B89451D201967B4007DE474 /* Disk.cpp in Sources */, + 4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */, 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, @@ -5230,7 +5292,7 @@ 4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */, 4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */, 4B2E86E325DC95150024F1E9 /* Joystick.cpp in Sources */, - 4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */, + 4BEBFB4E2002C4BF000708CC /* FAT12.cpp in Sources */, 4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */, 4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */, 4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */, @@ -5248,6 +5310,7 @@ 4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */, 4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */, 4BE21200253FC80900435408 /* StaticAnalyser.cpp in Sources */, + 4B051CB72680158600CA44E8 /* EXDos.cpp in Sources */, 4B8318B222D3E53C006DB630 /* Video.cpp in Sources */, 4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */, 4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */, @@ -5292,6 +5355,7 @@ 4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */, 4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */, 4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */, + 4B051CAD26783E2000CA44E8 /* Nick.cpp in Sources */, 4B89451F201967B4007DE474 /* Tape.cpp in Sources */, 4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */, 4BEDA3BC25B25563000C2DBD /* Decoder.cpp in Sources */, @@ -5338,6 +5402,7 @@ 4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */, 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B302185208A550100773308 /* DiskII.cpp in Sources */, + 4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */, 4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, 4B89452D201967B4007DE474 /* Tape.cpp in Sources */, @@ -5461,6 +5526,7 @@ 4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */, 4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */, 4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */, + 4BFEA2EF2682A7B900EBF94C /* Dave.cpp in Sources */, 4B4518A21F75FD1C00926311 /* G64.cpp in Sources */, 4B89452C201967B4007DE474 /* Tape.cpp in Sources */, 4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */, @@ -5492,11 +5558,13 @@ 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, + 4B051CA22676F52200CA44E8 /* Enterprise.cpp in Sources */, 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */, 4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */, 4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */, + 4B051CB0267C1CA200CA44E8 /* Keyboard.cpp in Sources */, 4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */, 4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */, @@ -5505,6 +5573,7 @@ 4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */, 4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, + 4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */, 4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */, 4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, @@ -5538,7 +5607,7 @@ 4B894536201967B4007DE474 /* Z80.cpp in Sources */, 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */, 4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */, - 4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */, + 4BEBFB4D2002C4BF000708CC /* FAT12.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, 4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */, @@ -5549,9 +5618,11 @@ 4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */, 4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */, + 4B051CB62680158600CA44E8 /* EXDos.cpp in Sources */, 4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */, 4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */, 4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, + 4B051CAC26783E2000CA44E8 /* Nick.cpp in Sources */, 4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */, 4B0F1BDA2602FF9800B85C66 /* Video.cpp in Sources */, 4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */, @@ -5684,7 +5755,7 @@ 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4B778F6023A5F3460000D260 /* Disk.cpp in Sources */, 4B778F5C23A5F3070000D260 /* MSX.cpp in Sources */, - 4B778F0323A5EBB00000D260 /* MSXDSK.cpp in Sources */, + 4B778F0323A5EBB00000D260 /* FAT12.cpp in Sources */, 4B778F4023A5F1910000D260 /* z8530.cpp in Sources */, 4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */, 4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */, @@ -5692,6 +5763,7 @@ 4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */, 4B778F2223A5EDDD0000D260 /* PulseQueuedTape.cpp in Sources */, 4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */, + 4B051CB3267D3FF800CA44E8 /* EnterpriseNickTests.mm in Sources */, 4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */, 4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */, 4B778F5623A5F2AF0000D260 /* CPM.cpp in Sources */, @@ -6055,7 +6127,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = DV3346VVUN; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(USER_LIBRARY_DIR)/Frameworks", @@ -6105,7 +6177,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = DV3346VVUN; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(USER_LIBRARY_DIR)/Frameworks", diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 2b77bafce..04eb4197f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -85,8 +85,12 @@ isEnabled = "YES"> + argument = "--new=enterprise" + isEnabled = "YES"> + + + isEnabled = "NO"> + isEnabled = "NO"> + isEnabled = "NO"> diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 54b355ae8..85c8c1e2c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -73,6 +73,7 @@ debugDocumentVersioning = "YES" migratedStopOnEveryIssue = "YES" debugServiceExtension = "internal" + enableGPUShaderValidationMode = "2" allowLocationSimulation = "NO"> diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index e82adb720..988fd9075 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -41,6 +41,30 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) { CSMachineCPCModel6128 }; +typedef NS_ENUM(NSInteger, CSMachineEnterpriseModel) { + CSMachineEnterpriseModel64, + CSMachineEnterpriseModel128, + CSMachineEnterpriseModel256, +}; + +typedef NS_ENUM(NSInteger, CSMachineEnterpriseEXOS) { + CSMachineEnterpriseEXOSVersion21, + CSMachineEnterpriseEXOSVersion20, + CSMachineEnterpriseEXOSVersion10, +}; + +typedef NS_ENUM(NSInteger, CSMachineEnterpriseBASIC) { + CSMachineEnterpriseBASICVersion21, + CSMachineEnterpriseBASICVersion11, + CSMachineEnterpriseBASICVersion10, + CSMachineEnterpriseBASICNone, +}; + +typedef NS_ENUM(NSInteger, CSMachineEnterpriseDOS) { + CSMachineEnterpriseDOSEXDOS, + CSMachineEnterpriseDOSNone, +}; + typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) { CSMachineMacintoshModel128k, CSMachineMacintoshModel512k, @@ -96,6 +120,7 @@ typedef int Kilobytes; - (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize; - (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model; - (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM; +- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos; - (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model; - (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive; - (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index acc8dd852..805fff5d1 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -19,6 +19,7 @@ #include "../../../../../Analyser/Static/AppleIIgs/Target.hpp" #include "../../../../../Analyser/Static/AtariST/Target.hpp" #include "../../../../../Analyser/Static/Commodore/Target.hpp" +#include "../../../../../Analyser/Static/Enterprise/Target.hpp" #include "../../../../../Analyser/Static/Macintosh/Target.hpp" #include "../../../../../Analyser/Static/MSX/Target.hpp" #include "../../../../../Analyser/Static/Oric/Target.hpp" @@ -130,6 +131,45 @@ return self; } +- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos { + self = [super init]; + if(self) { + using Target = Analyser::Static::Enterprise::Target; + auto target = std::make_unique(); + + switch(model) { + case CSMachineEnterpriseModel64: target->model = Target::Model::Enterprise64; break; + default: + case CSMachineEnterpriseModel128: target->model = Target::Model::Enterprise128; break; + case CSMachineEnterpriseModel256: target->model = Target::Model::Enterprise256; break; + } + + switch(exosVersion) { + case CSMachineEnterpriseEXOSVersion21: target->exos_version = Target::EXOSVersion::v21; break; + default: + case CSMachineEnterpriseEXOSVersion20: target->exos_version = Target::EXOSVersion::v20; break; + case CSMachineEnterpriseEXOSVersion10: target->exos_version = Target::EXOSVersion::v10; break; + } + + switch(basicVersion) { + case CSMachineEnterpriseBASICNone: target->basic_version = Target::BASICVersion::None; break; + default: + case CSMachineEnterpriseBASICVersion21: target->basic_version = Target::BASICVersion::v21; break; + case CSMachineEnterpriseBASICVersion11: target->basic_version = Target::BASICVersion::v11; break; + case CSMachineEnterpriseBASICVersion10: target->basic_version = Target::BASICVersion::v10; break; + } + + switch(dos) { + case CSMachineEnterpriseDOSEXDOS: target->dos = Target::DOS::EXDOS; break; + default: + case CSMachineEnterpriseDOSNone: target->dos = Target::DOS::None; break; + } + + _targets.push_back(std::move(target)); + } + return self; +} + - (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model { self = [super init]; if(self) { diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index cc68c2170..494e5dcdc 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1,8 +1,8 @@ - + - + @@ -17,10 +17,10 @@ - + - + - + @@ -369,7 +489,7 @@ Gw - + @@ -380,9 +500,9 @@ Gw - + - + @@ -392,11 +512,11 @@ Gw - + - + @@ -404,7 +524,7 @@ Gw - + @@ -418,7 +538,7 @@ Gw - + @@ -434,7 +554,7 @@ Gw - + @@ -443,7 +563,7 @@ Gw - + @@ -453,17 +573,17 @@ Gw - + - + - + @@ -479,7 +599,7 @@ Gw - + @@ -493,7 +613,7 @@ Gw - + @@ -501,7 +621,7 @@ Gw - + @@ -509,7 +629,7 @@ Gw