1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-01 14:29:51 +00:00

Merge pull request #950 from TomHarte/Enterprise

Adds emulation of the Enterprise
This commit is contained in:
Thomas Harte 2021-06-29 21:37:38 -04:00 committed by GitHub
commit c04a395499
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 3286 additions and 313 deletions

View File

@ -19,6 +19,7 @@ enum class Machine {
AtariST,
ColecoVision,
Electron,
Enterprise,
Macintosh,
MasterSystem,
MSX,

View File

@ -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<Analyser::Static::Target>(target));
return targets;
}

View File

@ -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 <string>
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 */

View File

@ -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<Target> {
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 */

View File

@ -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<Storage::Disk::MacintoshIMG>, 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<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format( "hfe",
@ -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<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
@ -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);

View File

@ -213,7 +213,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
/*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
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);

View File

@ -103,9 +103,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
}
if constexpr (has_sequence_points<T>::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 <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the amount of time since the object was last flushed, in the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
if constexpr (divider == 1) {
return time_since_update_;
}
return TargetTimeScale(time_since_update_.as_integral() / divider);
}
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
/// converted to the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
if constexpr (divider == 1) {
return time_since_update_ + offset;
}
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
}
/// Flushes all accumulated time.
///
/// This does not affect this actor's record of when the next sequence point will occur.
@ -185,7 +193,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
/// supports sequence points; @c LocalTimeScale() otherwise.
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
return time_until_event_;
return time_until_event_ / divider;
}
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
@ -196,10 +204,43 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
return rhs >= 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<T>::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<LocalTimeScale, TargetTimeScale>) {
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));
}
}

View File

@ -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;

View File

@ -9,7 +9,7 @@
#ifndef CachingExecutor_hpp
#define CachingExecutor_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <array>
#include <cstdint>

View File

@ -9,7 +9,7 @@
#ifndef Disassembler_hpp
#define Disassembler_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <list>
#include <map>

View File

@ -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;

View File

@ -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<int>());
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
// Update the programmable-frequency interrupt.
if(rate_ < InterruptRate::ToneGenerator0) {
programmable_offset_ -= cycles.as<int>();
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<int>();
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);
}

View File

@ -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 <cstdint>
#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 */

View File

@ -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<Storage::Disk::Disk> 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);
});
}

View File

@ -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<Storage::Disk::Disk> 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 */

View File

@ -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 <bool has_disk_controller> 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<uint8_t, 256 * 1024> ram_{};
std::array<uint8_t, 64 * 1024> exos_;
std::array<uint8_t, 16 * 1024> basic_;
std::array<uint8_t, 16 * 1024> exdos_rom_;
std::array<uint8_t, 32 * 1024> 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 <size_t slot> void page(uint8_t offset) {
pages_[slot] = offset;
#define Map(location, source) \
if(offset >= location && offset < location + source.size() / 0x4000) { \
page<slot>(&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<slot>(&ram_[address], &ram_[address]);
return;
}
page<slot>(nullptr, nullptr);
}
template <size_t slot> 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<uint8_t, 10> 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<ConcreteMachine, false, false> z80_;
JustInTimeActor<Nick, HalfCycles, 40434603, 11360000> nick_;
bool previous_nick_interrupt_line_ = false;
// Cf. timing guesses above.
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave::Audio dave_audio_;
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
// The following two should both use the same divider.
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> 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<const Target *>(target);
if(enterprise_target->dos == Target::DOS::None)
return new Enterprise::ConcreteMachine<false>(*enterprise_target, rom_fetcher);
else
return new Enterprise::ConcreteMachine<true>(*enterprise_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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 <cstdio>
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<const uint16_t *>(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<int>()) & 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<int>();
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<x, false>
#define lpixel(x) output_pixel<x, true>
#define ch256(x) output_character<x, 8>
#define ch128(x) output_character<x, 7>
#define ch64(x) output_character<x, 6>
#define attr(x) output_attributed<x>
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<uint16_t *>(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<uint16_t *>(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 <int bpp, bool is_lpixel> 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 <int bpp, int index_bits> 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 <int bpp> 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

View File

@ -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 <cstdint>
#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 <int bpp, bool is_lpixel> void output_pixel(uint16_t *target, int columns) const;
template <int bpp, int index_bits> void output_character(uint16_t *target, int columns) const;
template <int bpp> void output_attributed(uint16_t *target, int columns) const;
};
}
#endif /* Nick_hpp */

View File

@ -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>(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<std::string> 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<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
Add(AppleIIgs);
Add(AtariST);
AddMapped(Electron, Acorn);
Add(Enterprise);
Add(Macintosh);
Add(MSX);
Add(Oric);

View File

@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> filenames = {
"basic21.bin",
"BASIC Interpreter v2.1 (1985)(Intelligent Software).bin",
"BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin"
};
const std::initializer_list<uint32_t> 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<std::string> 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<std::string> 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: {

View File

@ -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<Node> children;
bool empty() const {
return type == Type::One && name == Name::None;
}
void add_descriptions(std::vector<Description> &) const;
bool validate(Map &) const;
void visit(

View File

@ -12,6 +12,8 @@
#include <cstdint>
#include <cstdlib>
#include "Sizes.hpp"
namespace Numeric {
template <typename IntType> struct LSFRPolynomial {};
@ -67,8 +69,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
determining the bit that was just shifted out.
*/
IntType next() {
const auto result = value_ & 1;
value_ = (value_ >> 1) ^ (result * polynomial);
const auto result = IntType(value_ & 1);
value_ = IntType((value_ >> 1) ^ (result * polynomial));
return result;
}
@ -76,6 +78,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
IntType value_ = 0;
};
template <uint64_t polynomial> class LFSRv: public LFSR<typename MinIntTypeValue<polynomial>::type, polynomial> {};
}
#endif /* LFSR_h */

View File

@ -10,6 +10,7 @@
#define Sizes_h
#include <limits>
#include <type_traits>
/*!
Maps to the smallest integral type that can contain max_value, from the following options:

View File

@ -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 = "<group>"; };
4B051C96266EF5F600CA44E8 /* CSAppleII.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAppleII.mm; sourceTree = "<group>"; };
4B051C98266EF60500CA44E8 /* CSAppleII.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSAppleII.h; sourceTree = "<group>"; };
4B051CA02676F52200CA44E8 /* Enterprise.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Enterprise.hpp; sourceTree = "<group>"; };
4B051CA12676F52200CA44E8 /* Enterprise.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Enterprise.cpp; sourceTree = "<group>"; };
4B051CA526781D6500CA44E8 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B051CA626781D6500CA44E8 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B051CAA26783E2000CA44E8 /* Nick.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Nick.cpp; sourceTree = "<group>"; };
4B051CAB26783E2000CA44E8 /* Nick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Nick.hpp; sourceTree = "<group>"; };
4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterpriseNickTests.mm; sourceTree = "<group>"; };
4B051CB42680158600CA44E8 /* EXDos.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EXDos.cpp; sourceTree = "<group>"; };
4B051CB52680158600CA44E8 /* EXDos.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EXDos.hpp; sourceTree = "<group>"; };
4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; };
4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
@ -1998,8 +2023,8 @@
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SoundGenerator.cpp; sourceTree = "<group>"; };
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundGenerator.hpp; sourceTree = "<group>"; };
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSXDSK.cpp; sourceTree = "<group>"; };
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = "<group>"; };
4BEBFB4B2002C4BF000708CC /* FAT12.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FAT12.cpp; sourceTree = "<group>"; };
4BEBFB4C2002C4BF000708CC /* FAT12.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FAT12.hpp; sourceTree = "<group>"; };
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskROM.cpp; sourceTree = "<group>"; };
4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskROM.hpp; sourceTree = "<group>"; };
4BEDA3B425B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
@ -2049,6 +2074,9 @@
4BFDD78A1F7F2DB4008579B9 /* ImplicitSectors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ImplicitSectors.hpp; sourceTree = "<group>"; };
4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImplicitSectors.cpp; sourceTree = "<group>"; };
4BFE7B861FC39BF100160B38 /* StandardOptions.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StandardOptions.hpp; sourceTree = "<group>"; };
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Dave.cpp; sourceTree = "<group>"; };
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Dave.hpp; sourceTree = "<group>"; };
4BFEA2F12682A90200EBF94C /* Sizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Sizes.hpp; sourceTree = "<group>"; };
4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = "<group>"; };
@ -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 = "<group>";
};
4B051CA426781D6500CA44E8 /* Enterprise */ = {
isa = PBXGroup;
children = (
4B051CA726781D6500CA44E8 /* StaticAnalyser.cpp */,
4B051CA526781D6500CA44E8 /* StaticAnalyser.hpp */,
4B051CA626781D6500CA44E8 /* Target.hpp */,
);
path = Enterprise;
sourceTree = "<group>";
};
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 = "<group>";
@ -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 = "<group>";
@ -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",

View File

@ -85,8 +85,12 @@
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=amstradcpc"
isEnabled = "NO">
argument = "--new=enterprise"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--basic-version=any"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col&quot;"
@ -126,15 +130,15 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--model=mac512k"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=macintosh"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--use-square-pixels"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>

View File

@ -73,6 +73,7 @@
debugDocumentVersioning = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
enableGPUShaderValidationMode = "2"
allowLocationSimulation = "NO">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@ -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;

View File

@ -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<Target>();
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) {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -17,10 +17,10 @@
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="316"/>
<rect key="contentRect" x="196" y="240" width="590" height="353"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="316"/>
<rect key="frame" x="0.0" y="0.0" width="590" height="353"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
@ -59,16 +59,16 @@ Gw
</textFieldCell>
</textField>
<tabView type="noTabsBezelBorder" translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
<rect key="frame" x="154" y="56" width="420" height="206"/>
<rect key="frame" x="154" y="56" width="420" height="243"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
<rect key="frame" x="18" y="163" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -76,7 +76,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
<rect key="frame" x="18" y="133" width="96" height="16"/>
<rect key="frame" x="18" y="155" width="96" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -84,7 +84,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
<rect key="frame" x="67" y="157" width="117" height="25"/>
<rect key="frame" x="67" y="179" width="117" height="25"/>
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -99,7 +99,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
<rect key="frame" x="117" y="127" width="134" height="25"/>
<rect key="frame" x="117" y="149" width="134" height="25"/>
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -115,11 +115,11 @@ Gw
</subviews>
<constraints>
<constraint firstItem="WnO-ef-IC6" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="5RF-1w-9HW"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="5" id="865-cv-qVk"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="20" symbolic="YES" id="865-cv-qVk"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="trailing" constant="20" symbolic="YES" id="9GL-al-1qi"/>
<constraint firstItem="WnO-ef-IC6" firstAttribute="centerY" secondItem="LSB-WP-FMi" secondAttribute="centerY" id="Fuj-zT-MIm"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="jli-ac-Sij" secondAttribute="trailing" constant="20" symbolic="YES" id="I8d-OR-ICN"/>
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="5" id="Qi1-CV-A0c"/>
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="20" symbolic="YES" id="Qi1-CV-A0c"/>
<constraint firstItem="V5Z-dX-Ns4" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="SWc-iX-1We"/>
<constraint firstItem="LSB-WP-FMi" firstAttribute="leading" secondItem="WnO-ef-IC6" secondAttribute="trailing" constant="8" symbolic="YES" id="bte-XA-xNQ"/>
<constraint firstItem="LSB-WP-FMi" firstAttribute="top" secondItem="jli-ac-Sij" secondAttribute="bottom" constant="10" symbolic="YES" id="ki5-JR-vRe"/>
@ -129,12 +129,12 @@ Gw
</view>
</tabViewItem>
<tabViewItem label="Apple IIgs" identifier="appleiigs" id="u5E-8n-ghF">
<view key="view" ambiguous="YES" id="jOM-9f-vkk">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<view key="view" id="jOM-9f-vkk">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="kiv-1P-FWc">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -142,7 +142,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
<rect key="frame" x="18" y="23" width="85" height="16"/>
<rect key="frame" x="18" y="155" width="85" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory size:" id="OLJ-nC-yyj">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -150,7 +150,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gcS-uy-mzl">
<rect key="frame" x="67" y="47" width="89" height="25"/>
<rect key="frame" x="67" y="179" width="89" height="25"/>
<popUpButtonCell key="cell" type="push" title="ROM 03" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="axesIndependently" inset="2" selectedItem="0TS-DO-O9h" id="hjw-g8-e2f">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -164,7 +164,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nQa-YS-utT">
<rect key="frame" x="106" y="17" width="82" height="25"/>
<rect key="frame" x="106" y="149" width="82" height="25"/>
<popUpButtonCell key="cell" type="push" title="8 mb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8192" imageScaling="axesIndependently" inset="2" selectedItem="UHg-gU-Xnn" id="dl3-cq-uWO">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -182,24 +182,24 @@ Gw
<constraint firstItem="LES-76-Ovz" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="2D3-Ve-6CN"/>
<constraint firstItem="0d9-IG-gKU" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="8Xz-86-tDf"/>
<constraint firstItem="0d9-IG-gKU" firstAttribute="centerY" secondItem="gcS-uy-mzl" secondAttribute="centerY" id="Eww-Qq-eBT"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="5" id="F6i-cP-7AR"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="20" symbolic="YES" id="F6i-cP-7AR"/>
<constraint firstItem="gcS-uy-mzl" firstAttribute="leading" secondItem="0d9-IG-gKU" secondAttribute="trailing" constant="8" symbolic="YES" id="LUm-rI-LYP"/>
<constraint firstItem="LES-76-Ovz" firstAttribute="centerY" secondItem="nQa-YS-utT" secondAttribute="centerY" id="UdP-U6-OFE"/>
<constraint firstItem="nQa-YS-utT" firstAttribute="leading" secondItem="LES-76-Ovz" secondAttribute="trailing" constant="8" symbolic="YES" id="Xm1-iG-Dgj"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gcS-uy-mzl" secondAttribute="trailing" constant="20" symbolic="YES" id="a38-Cx-CEh"/>
<constraint firstItem="nQa-YS-utT" firstAttribute="top" secondItem="gcS-uy-mzl" secondAttribute="bottom" constant="10" symbolic="YES" id="aM9-m8-s7z"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="trailing" constant="20" symbolic="YES" id="cqx-Jc-rUb"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="5" id="sbT-If-NTU"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="20" symbolic="YES" id="sbT-If-NTU"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
<rect key="frame" x="67" y="47" width="96" height="25"/>
<rect key="frame" x="67" y="179" width="96" height="25"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -213,7 +213,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -224,8 +224,8 @@ Gw
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="20" symbolic="YES" id="4AF-5C-2IF"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="20" symbolic="YES" id="Wof-5h-gfD"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="5" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="5" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="20" symbolic="YES" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="20" symbolic="YES" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" symbolic="YES" id="mA8-US-ndo"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
</constraints>
@ -233,11 +233,11 @@ Gw
</tabViewItem>
<tabViewItem label="Atari ST" identifier="atarist" id="a6Y-mx-yFn">
<view key="view" id="nnv-Wi-7hc">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nLf-LI-nWO">
<rect key="frame" x="18" y="57" width="728" height="16"/>
<rect key="frame" x="18" y="204" width="364" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="At present only a 512k Atari ST is supported." id="gBA-ke-mur">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -254,32 +254,32 @@ Gw
</tabViewItem>
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
<view key="view" id="SRc-2D-95G">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
<rect key="frame" x="18" y="56" width="168" height="18"/>
<rect key="frame" x="18" y="186" width="168" height="18"/>
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
<rect key="frame" x="18" y="34" width="232" height="18"/>
<rect key="frame" x="18" y="164" width="232" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cG2-Ph-S3Z">
<rect key="frame" x="18" y="12" width="231" height="18"/>
<rect key="frame" x="18" y="142" width="231" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Plus 6 Utility ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="yjF-XS-zx6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
<rect key="frame" x="18" y="-10" width="284" height="18"/>
<rect key="frame" x="18" y="120" width="284" height="18"/>
<buttonCell key="cell" type="check" title="Fill unused ROM banks with sideways RAM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="JEz-eK-uWp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -289,13 +289,13 @@ Gw
<constraints>
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="1iM-70-oZq"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="E9b-RP-9vj"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="5" id="FM6-AA-Vhf"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="20" symbolic="YES" id="FM6-AA-Vhf"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="NfY-dE-aJw"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="cG2-Ph-S3Z" secondAttribute="bottom" constant="6" symbolic="YES" id="S45-42-Gtv"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="trailing" constant="20" symbolic="YES" id="X3p-qJ-ENH"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="b5a-SX-2ty"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="trailing" constant="20" symbolic="YES" id="dmY-PV-ap4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="3" id="ggl-QH-mV4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="20" symbolic="YES" id="ggl-QH-mV4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cG2-Ph-S3Z" secondAttribute="trailing" constant="20" symbolic="YES" id="m6t-dP-71d"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" symbolic="YES" id="mvO-UZ-BtT"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="npw-IZ-6xU"/>
@ -303,13 +303,133 @@ Gw
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Enterprise" identifier="enterprise" id="zhO-EO-wUe">
<view key="view" id="1cs-PX-RAH">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PhH-bu-pb5">
<rect key="frame" x="80" y="179" width="129" height="25"/>
<popUpButtonCell key="cell" type="push" title="Enterprise 128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="128" imageScaling="axesIndependently" inset="2" selectedItem="roH-nL-f8o" id="z9O-XC-XBv">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="ojI-Vb-iHz">
<items>
<menuItem title="Enterprise 256" tag="256" id="Al3-A0-tvw"/>
<menuItem title="Enterprise 128" state="on" tag="128" id="roH-nL-f8o"/>
<menuItem title="Enterprise 64" tag="64" id="vNG-Tv-bDI"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nen-Za-7zH">
<rect key="frame" x="64" y="149" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="uNC-hA-d5z">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="Qja-xZ-wVM"/>
<menuItem title="Version 2.0" tag="20" id="XTj-l7-KX3"/>
<menuItem title="Version 1.0" tag="10" id="Ky2-2D-wcY"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-GH-7xi">
<rect key="frame" x="67" y="119" width="105" height="25"/>
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="VcH-Xm-1hY">
<items>
<menuItem title="Version 2.1" state="on" tag="21" id="TME-cv-Jh1"/>
<menuItem title="Version 1.1" tag="11" id="7P2-aF-6fp"/>
<menuItem title="Version 1.0" tag="10" id="j8p-uY-BhG"/>
<menuItem title="None" id="eGk-Bj-IVT"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syE-e7-TjU">
<rect key="frame" x="57" y="89" width="83" height="25"/>
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="DOS" id="sdr-al-7mi">
<items>
<menuItem title="EXDOS" tag="1" id="8rP-2w-PdU"/>
<menuItem title="None" state="on" id="qoS-KO-iEZ"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
<rect key="frame" x="18" y="155" width="43" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
<rect key="frame" x="18" y="185" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Machine:" id="uTv-hH-mIC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
<rect key="frame" x="18" y="125" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
<rect key="frame" x="18" y="95" width="36" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="dzd-tH-BjX" firstAttribute="centerY" secondItem="hIr-GH-7xi" secondAttribute="centerY" id="3TV-RU-Kgh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="hIr-GH-7xi" secondAttribute="trailing" constant="20" symbolic="YES" id="44v-9O-y7L"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="centerY" secondItem="PhH-bu-pb5" secondAttribute="centerY" id="6Wc-aR-wuL"/>
<constraint firstItem="dzd-tH-BjX" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="7RZ-Om-TAa"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="centerY" secondItem="nen-Za-7zH" secondAttribute="centerY" id="CLa-6E-8BB"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="ENF-TY-TQ7"/>
<constraint firstItem="nen-Za-7zH" firstAttribute="leading" secondItem="ykc-W1-YaS" secondAttribute="trailing" constant="8" symbolic="YES" id="GWR-VI-9PG"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="bottom" constant="20" symbolic="YES" id="K3s-FA-zMB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="PhH-bu-pb5" secondAttribute="trailing" constant="20" symbolic="YES" id="LwB-ef-uF4"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="top" secondItem="1cs-PX-RAH" secondAttribute="top" constant="20" symbolic="YES" id="Myt-i4-jVq"/>
<constraint firstItem="pxr-Bq-yh0" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="Qzp-IY-Pa0"/>
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="T3e-u7-fiQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="trailing" constant="20" symbolic="YES" id="TEX-Nw-y2K"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="TgR-RR-eA1"/>
<constraint firstItem="pxr-Bq-yh0" firstAttribute="centerY" secondItem="syE-e7-TjU" secondAttribute="centerY" id="UYw-uz-Am0"/>
<constraint firstItem="hIr-GH-7xi" firstAttribute="top" secondItem="nen-Za-7zH" secondAttribute="bottom" constant="10" symbolic="YES" id="VOc-2v-s3u"/>
<constraint firstItem="syE-e7-TjU" firstAttribute="top" secondItem="hIr-GH-7xi" secondAttribute="bottom" constant="10" symbolic="YES" id="W9S-on-Heq"/>
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Yes-o3-V0m"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="bAP-lx-pdi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nen-Za-7zH" secondAttribute="trailing" constant="20" symbolic="YES" id="eEI-5Q-TnO"/>
<constraint firstItem="syE-e7-TjU" firstAttribute="leading" secondItem="pxr-Bq-yh0" secondAttribute="trailing" constant="8" symbolic="YES" id="fmM-Ma-Jyu"/>
<constraint firstItem="hIr-GH-7xi" firstAttribute="leading" secondItem="dzd-tH-BjX" secondAttribute="trailing" constant="8" symbolic="YES" id="jDQ-TF-Ogf"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
<view key="view" id="7Yf-vi-Q0W">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -317,7 +437,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xa6-NA-JY5">
<rect key="frame" x="67" y="47" width="75" height="25"/>
<rect key="frame" x="67" y="179" width="75" height="25"/>
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="axesIndependently" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -333,9 +453,9 @@ Gw
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="5" id="3hY-Ca-mnR"/>
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="20" symbolic="YES" id="3hY-Ca-mnR"/>
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="20" symbolic="YES" id="5s6-87-VT6"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="5" id="KYf-GJ-Y7k"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="20" symbolic="YES" id="KYf-GJ-Y7k"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="trailing" constant="20" symbolic="YES" id="LZ5-xH-fU0"/>
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="centerY" secondItem="xa6-NA-JY5" secondAttribute="centerY" id="Wa5-KX-3Me"/>
<constraint firstItem="xa6-NA-JY5" firstAttribute="leading" secondItem="ZOY-4E-Cfl" secondAttribute="trailing" constant="8" symbolic="YES" id="ktS-sr-F8L"/>
@ -344,18 +464,18 @@ Gw
</tabViewItem>
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
<view key="view" id="mWD-An-tR7">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
<rect key="frame" x="18" y="26" width="128" height="18"/>
<rect key="frame" x="18" y="158" width="128" height="18"/>
<buttonCell key="cell" type="check" title="Attach disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CB3-nA-VTM">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LG6-mP-SeG">
<rect key="frame" x="71" y="47" width="146" height="25"/>
<rect key="frame" x="71" y="179" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -369,7 +489,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<rect key="frame" x="18" y="53" width="50" height="16"/>
<rect key="frame" x="18" y="185" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -380,9 +500,9 @@ Gw
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="20" symbolic="YES" id="0Oc-n7-gaM"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="8" symbolic="YES" id="LBt-4m-GDc"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="5" id="bcb-ZZ-VpQ"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="20" symbolic="YES" id="bcb-ZZ-VpQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="20" symbolic="YES" id="l8P-UW-8ig"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="3" id="mga-YX-Bek"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="20" symbolic="YES" id="mga-YX-Bek"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="q8Q-kh-Opj"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="leading" secondItem="ZaD-7v-rMS" secondAttribute="trailing" constant="8" symbolic="YES" id="svb-nH-GlP"/>
<constraint firstItem="ZaD-7v-rMS" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="zgh-a5-FNF"/>
@ -392,11 +512,11 @@ Gw
</tabViewItem>
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
<view key="view" id="sOR-e0-8iZ">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -404,7 +524,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ENP-hI-BVZ">
<rect key="frame" x="67" y="47" width="107" height="25"/>
<rect key="frame" x="67" y="179" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="Oric-1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="jGN-1a-biF" id="Jll-EJ-cMr">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -418,7 +538,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
<rect key="frame" x="113" y="17" width="130" height="25"/>
<rect key="frame" x="113" y="149" width="130" height="25"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -434,7 +554,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
<rect key="frame" x="18" y="23" width="92" height="16"/>
<rect key="frame" x="18" y="155" width="92" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -443,7 +563,7 @@ Gw
</textField>
</subviews>
<constraints>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="5" id="Bgq-8R-8I4"/>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="20" symbolic="YES" id="Bgq-8R-8I4"/>
<constraint firstItem="fYL-p6-wyn" firstAttribute="leading" secondItem="okM-ZI-NbF" secondAttribute="trailing" constant="8" symbolic="YES" id="Pra-lC-JPB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ENP-hI-BVZ" secondAttribute="trailing" constant="20" symbolic="YES" id="Sr5-QZ-xU3"/>
<constraint firstItem="ENP-hI-BVZ" firstAttribute="leading" secondItem="0ct-tf-uRH" secondAttribute="trailing" constant="8" symbolic="YES" id="Wcg-1P-z6l"/>
@ -453,17 +573,17 @@ Gw
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="trailing" constant="20" symbolic="YES" id="ewV-Dw-zj2"/>
<constraint firstItem="0ct-tf-uRH" firstAttribute="leading" secondItem="sOR-e0-8iZ" secondAttribute="leading" constant="20" symbolic="YES" id="huH-9L-97Y"/>
<constraint firstItem="fYL-p6-wyn" firstAttribute="top" secondItem="ENP-hI-BVZ" secondAttribute="bottom" constant="10" symbolic="YES" id="l3T-Ve-0Jw"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="5" id="mN9-AZ-wSn"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="20" symbolic="YES" id="mN9-AZ-wSn"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
<view key="view" id="fLI-XB-QCr">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
<rect key="frame" x="71" y="47" width="146" height="25"/>
<rect key="frame" x="71" y="179" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="45i-0n-gau" id="yi7-eo-I0q">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -479,7 +599,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2eV-Us-eEv">
<rect key="frame" x="108" y="17" width="116" height="25"/>
<rect key="frame" x="108" y="149" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="fOl-8Q-fsA" id="rH0-7T-pJE">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -493,7 +613,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
<rect key="frame" x="18" y="53" width="50" height="16"/>
<rect key="frame" x="18" y="185" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -501,7 +621,7 @@ Gw
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
<rect key="frame" x="18" y="23" width="87" height="16"/>
<rect key="frame" x="18" y="155" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -509,7 +629,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lrf-gL-6EI">
<rect key="frame" x="18" y="-4" width="177" height="18"/>
<rect key="frame" x="18" y="128" width="177" height="18"/>
<buttonCell key="cell" type="check" title="Attach C-1540 disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tsq-YD-xw8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -521,7 +641,7 @@ Gw
<constraint firstItem="MTh-9p-FqC" firstAttribute="centerY" secondItem="ueK-gq-gaF" secondAttribute="centerY" id="8KD-Bm-KRA"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="leading" secondItem="MTh-9p-FqC" secondAttribute="trailing" constant="8" symbolic="YES" id="9m6-6j-8j2"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="trailing" constant="20" symbolic="YES" id="M08-mP-Plz"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="5" id="XN3-GK-BX9"/>
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="20" symbolic="YES" id="XN3-GK-BX9"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ueK-gq-gaF" secondAttribute="trailing" constant="20" symbolic="YES" id="YZ9-7N-ssA"/>
<constraint firstItem="MTh-9p-FqC" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="bgZ-k9-IQC"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2eV-Us-eEv" secondAttribute="trailing" constant="20" symbolic="YES" id="eiB-vH-17d"/>
@ -529,18 +649,18 @@ Gw
<constraint firstItem="2eV-Us-eEv" firstAttribute="leading" secondItem="gRS-DK-rIy" secondAttribute="trailing" constant="8" symbolic="YES" id="hQ9-uy-KHg"/>
<constraint firstItem="gRS-DK-rIy" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="iyp-1K-rdC"/>
<constraint firstItem="gRS-DK-rIy" firstAttribute="centerY" secondItem="2eV-Us-eEv" secondAttribute="centerY" id="qWf-Nb-PfJ"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="3" id="vNb-Sw-Mxw"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="20" symbolic="YES" id="vNb-Sw-Mxw"/>
<constraint firstItem="Lrf-gL-6EI" firstAttribute="top" secondItem="2eV-Us-eEv" secondAttribute="bottom" constant="8" symbolic="YES" id="zBX-Qq-j5f"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
<view key="view" id="8hL-Vn-Hg0">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
<rect key="frame" x="108" y="47" width="116" height="25"/>
<rect key="frame" x="108" y="179" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -554,7 +674,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<rect key="frame" x="18" y="53" width="87" height="16"/>
<rect key="frame" x="18" y="185" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -562,7 +682,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
<rect key="frame" x="18" y="26" width="118" height="18"/>
<rect key="frame" x="18" y="158" width="118" height="18"/>
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -574,8 +694,8 @@ Gw
<constraint firstItem="NCX-4e-lSu" firstAttribute="centerY" secondItem="I1a-Eu-5UB" secondAttribute="centerY" id="1ve-sc-QwI"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="leading" secondItem="NCX-4e-lSu" secondAttribute="trailing" constant="8" symbolic="YES" id="Bu6-60-74x"/>
<constraint firstItem="NCX-4e-lSu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="W09-iG-ARI"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="5" id="fkf-wO-Q8i"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="3" id="fmT-7E-hUT"/>
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="20" symbolic="YES" id="fkf-wO-Q8i"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="20" symbolic="YES" id="fmT-7E-hUT"/>
<constraint firstItem="ReP-bV-Thu" firstAttribute="top" secondItem="I1a-Eu-5UB" secondAttribute="bottom" constant="8" symbolic="YES" id="hYk-xC-63o"/>
<constraint firstItem="ReP-bV-Thu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="qen-KS-rWi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="trailing" constant="20" symbolic="YES" id="r7F-DT-oRc"/>
@ -584,11 +704,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
<view key="view" id="bmd-gL-gzT">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
<rect key="frame" x="108" y="157" width="116" height="25"/>
<rect key="frame" x="108" y="179" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -602,7 +722,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="163" width="87" height="16"/>
<rect key="frame" x="18" y="185" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -613,37 +733,37 @@ Gw
<constraints>
<constraint firstItem="8tU-73-XEE" firstAttribute="centerY" secondItem="5aO-UX-HnX" secondAttribute="centerY" id="1Jm-YL-OKU"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="trailing" constant="20" symbolic="YES" id="ALj-0x-bFb"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="5" id="LJ2-W9-fOf"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="20" symbolic="YES" id="LJ2-W9-fOf"/>
<constraint firstItem="8tU-73-XEE" firstAttribute="leading" secondItem="bmd-gL-gzT" secondAttribute="leading" constant="20" symbolic="YES" id="M6Y-jN-LAf"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="5" id="PPD-Jz-qCL"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="20" symbolic="YES" id="PPD-Jz-qCL"/>
<constraint firstItem="5aO-UX-HnX" firstAttribute="leading" secondItem="8tU-73-XEE" secondAttribute="trailing" constant="8" symbolic="YES" id="j1u-n4-2IK"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
<view key="view" id="bMx-F6-JUb">
<rect key="frame" x="10" y="7" width="400" height="186"/>
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
<rect key="frame" x="67" y="157" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<rect key="frame" x="67" y="179" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8lt-dk-zPr">
<items>
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="16kb" state="on" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+2a" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="163" width="46" height="16"/>
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -655,23 +775,23 @@ Gw
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="trailing" constant="20" symbolic="YES" id="90E-uI-MQg"/>
<constraint firstItem="fJ3-ma-Byy" firstAttribute="leading" secondItem="bMx-F6-JUb" secondAttribute="leading" constant="20" symbolic="YES" id="9kE-iQ-dxd"/>
<constraint firstItem="fJ3-ma-Byy" firstAttribute="centerY" secondItem="gFZ-d4-WFv" secondAttribute="centerY" id="LxG-5E-Q5Y"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="5" id="d8S-vX-B5e"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="20" symbolic="YES" id="d8S-vX-B5e"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="leading" secondItem="fJ3-ma-Byy" secondAttribute="trailing" constant="8" symbolic="YES" id="hKS-47-R2y"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="5" id="wsX-Wq-iPt"/>
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="20" symbolic="YES" id="wsX-Wq-iPt"/>
</constraints>
</view>
</tabViewItem>
</tabViewItems>
</tabView>
<scrollView borderType="line" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="z5Q-Bs-hJj">
<rect key="frame" x="20" y="60" width="130" height="201"/>
<rect key="frame" x="20" y="60" width="130" height="238"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
<clipView key="contentView" id="O8s-Vw-9yQ">
<rect key="frame" x="1" y="1" width="128" height="199"/>
<rect key="frame" x="1" y="1" width="128" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" id="3go-Eb-GOy">
<rect key="frame" x="0.0" y="0.0" width="128" height="199"/>
<rect key="frame" x="0.0" y="0.0" width="128" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -708,7 +828,7 @@ Gw
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
<rect key="frame" x="18" y="269" width="554" height="27"/>
<rect key="frame" x="18" y="306" width="554" height="27"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose a machine" id="32m-Vs-dPO">
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -737,7 +857,7 @@ Gw
<constraint firstItem="9YM-5x-pc0" firstAttribute="top" secondItem="z5Q-Bs-hJj" secondAttribute="bottom" constant="14" id="suf-rn-Bmy"/>
</constraints>
</view>
<point key="canvasLocation" x="-1" y="88"/>
<point key="canvasLocation" x="-1" y="106.5"/>
</window>
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
<connections>
@ -750,6 +870,10 @@ Gw
<outlet property="electronAP6Button" destination="cG2-Ph-S3Z" id="vkq-1J-KBG"/>
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
<outlet property="electronSidewaysRAMButton" destination="lzo-8g-o4S" id="LtS-wv-tMf"/>
<outlet property="enterpriseBASICButton" destination="hIr-GH-7xi" id="fM6-It-9UO"/>
<outlet property="enterpriseDOSButton" destination="syE-e7-TjU" id="sCW-Bj-ZTW"/>
<outlet property="enterpriseEXOSButton" destination="nen-Za-7zH" id="NwS-ua-FdA"/>
<outlet property="enterpriseModelButton" destination="PhH-bu-pb5" id="8wD-sW-aBw"/>
<outlet property="machineNameTable" destination="3go-Eb-GOy" id="Ppf-S0-IP1"/>
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>

View File

@ -27,14 +27,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
@IBOutlet var appleIIgsModelButton: NSPopUpButton!
@IBOutlet var appleIIgsMemorySizeButton: NSPopUpButton!
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
// MARK: - Electron properties
@IBOutlet var electronDFSButton: NSButton!
@IBOutlet var electronADFSButton: NSButton!
@IBOutlet var electronAP6Button: NSButton!
@IBOutlet var electronSidewaysRAMButton: NSButton!
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
// MARK: - Enterprise properties
@IBOutlet var enterpriseModelButton: NSPopUpButton!
@IBOutlet var enterpriseEXOSButton: NSPopUpButton!
@IBOutlet var enterpriseBASICButton: NSPopUpButton!
@IBOutlet var enterpriseDOSButton: NSPopUpButton!
// MARK: - Macintosh properties
@IBOutlet var macintoshModelTypeButton: NSPopUpButton!
@ -93,14 +99,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
appleIIgsModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsModel"))
appleIIgsMemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsMemorySize"))
// CPC settings
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Electron settings
electronDFSButton.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off
electronADFSButton.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off
electronAP6Button.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off
electronSidewaysRAMButton.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off
// CPC settings
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
// Enterprise settings
enterpriseModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseModel"))
enterpriseEXOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseEXOSVersion"))
enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion"))
enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS"))
// Macintosh settings
macintoshModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
@ -143,14 +155,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
standardUserDefaults.set(appleIIgsModelButton.selectedTag(), forKey: "new.appleIIgsModel")
standardUserDefaults.set(appleIIgsMemorySizeButton.selectedTag(), forKey: "new.appleIIgsMemorySize")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
// Electron settings
standardUserDefaults.set(electronDFSButton.state == .on, forKey: "new.electronDFS")
standardUserDefaults.set(electronADFSButton.state == .on, forKey: "new.electronADFS")
standardUserDefaults.set(electronAP6Button.state == .on, forKey: "new.electronAP6")
standardUserDefaults.set(electronSidewaysRAMButton.state == .on, forKey: "new.electronSidewaysRAM")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
// Enterprise settings
standardUserDefaults.set(enterpriseModelButton.selectedTag(), forKey: "new.enterpriseModel")
standardUserDefaults.set(enterpriseEXOSButton.selectedTag(), forKey: "new.enterpriseEXOSVersion")
standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion")
standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS")
// Macintosh settings
standardUserDefaults.set(macintoshModelTypeButton.selectedTag(), forKey: "new.macintoshModel")
@ -205,12 +223,6 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
storeOptions()
switch machineSelector.selectedTabViewItem!.identifier as! String {
case "electron":
return CSStaticAnalyser(
electronDFS: electronDFSButton.state == .on,
adfs: electronADFSButton.state == .on,
ap6: electronAP6Button.state == .on,
sidewaysRAM: electronSidewaysRAMButton.state == .on)
case "appleii":
var model: CSMachineAppleIIModel = .appleII
@ -255,6 +267,48 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
}
case "electron":
return CSStaticAnalyser(
electronDFS: electronDFSButton.state == .on,
adfs: electronADFSButton.state == .on,
ap6: electronAP6Button.state == .on,
sidewaysRAM: electronSidewaysRAMButton.state == .on)
case "enterprise":
var model: CSMachineEnterpriseModel = .model128
switch enterpriseModelButton.selectedItem!.tag {
case 64: model = .model64
case 256: model = .model256
case 128: fallthrough
default: model = .model128
}
var exos: CSMachineEnterpriseEXOS = .version21
switch enterpriseEXOSButton.selectedItem!.tag {
case 10: exos = .version10
case 20: exos = .version20
case 21: fallthrough
default: exos = .version21
}
var basic: CSMachineEnterpriseBASIC = .version21
switch enterpriseBASICButton.selectedTag() {
case 0: basic = .none
case 10: basic = .version10
case 11: basic = .version11
case 21: fallthrough
default: basic = .version21
}
var dos: CSMachineEnterpriseDOS = .dosNone
switch enterpriseDOSButton.selectedTag() {
case 1: dos = .DOSEXDOS
case 0: fallthrough
default: dos = .dosNone
}
return CSStaticAnalyser(enterpriseModel: model, exosVersion: exos, basicVersion: basic, dos: dos)
case "mac":
switch macintoshModelTypeButton.selectedItem!.tag {
case 0: return CSStaticAnalyser(macintoshModel: .model128k)

View File

@ -0,0 +1,72 @@
//
// EnterpriseNickTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 18/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../Machines/Enterprise/Nick.hpp"
#include <memory>
@interface EnterpriseNickTests : XCTestCase
@end
@implementation EnterpriseNickTests {
std::unique_ptr<Enterprise::Nick> _nick;
uint8_t _ram[64*1024];
int _totalLines;
}
- (void)setUp {
[super setUp];
// Create a Nick.
_nick = std::make_unique<Enterprise::Nick>(_ram);
// Add a basic line table of blocks proceeding in length: 1, 2, 3, 4, etc and toggling the interrupt bit.
_totalLines = 0;
int nextLength = 0;
int pointer = 0;
uint8_t interruptFlag = 0x80;
while(nextLength < 256) {
_ram[pointer] = 0x100 - nextLength;
_ram[pointer+1] = interruptFlag;
pointer += 16;
++nextLength;
interruptFlag ^= 0x80;
_totalLines += nextLength;
}
// For now: assume Nick starts at address 0 from creation.
}
- (void)testInterruptPrediction {
// Run for the number of cycles implied by the number of lines.
int next_sequence_point = _nick->get_next_sequence_point().as<int>();
bool last_interrupt_line = _nick->get_interrupt_line();
for(int c = 0; c < _totalLines*912; c++) {
// Check that interrupt line transitions happen only on declared sequence points.
_nick->run_for(Cycles(1));
--next_sequence_point;
const bool interrupt_line = _nick->get_interrupt_line();
if(interrupt_line != last_interrupt_line) {
XCTAssertEqual(next_sequence_point, 0);
}
last_interrupt_line = interrupt_line;
if(!next_sequence_point) {
next_sequence_point = _nick->get_next_sequence_point().as<int>();
} else {
const int expected_next_sequence_point = _nick->get_next_sequence_point().as<int>();
XCTAssertEqual(next_sequence_point, expected_next_sequence_point);
}
}
}
@end

View File

@ -47,6 +47,7 @@ SOURCES += \
$$SRC/Analyser/Static/Commodore/*.cpp \
$$SRC/Analyser/Static/Disassembler/*.cpp \
$$SRC/Analyser/Static/DiskII/*.cpp \
$$SRC/Analyser/Static/Enterprise/*.cpp \
$$SRC/Analyser/Static/Macintosh/*.cpp \
$$SRC/Analyser/Static/MSX/*.cpp \
$$SRC/Analyser/Static/Oric/*.cpp \
@ -92,6 +93,7 @@ SOURCES += \
$$SRC/Machines/Commodore/1540/Implementation/*.cpp \
$$SRC/Machines/Commodore/Vic-20/*.cpp \
$$SRC/Machines/Electron/*.cpp \
$$SRC/Machines/Enterprise/*.cpp \
$$SRC/Machines/MasterSystem/*.cpp \
$$SRC/Machines/MSX/*.cpp \
$$SRC/Machines/Oric/*.cpp \
@ -166,6 +168,7 @@ HEADERS += \
$$SRC/Analyser/Static/Commodore/*.hpp \
$$SRC/Analyser/Static/Disassembler/*.hpp \
$$SRC/Analyser/Static/DiskII/*.hpp \
$$SRC/Analyser/Static/Enterprise/*.hpp \
$$SRC/Analyser/Static/Macintosh/*.hpp \
$$SRC/Analyser/Static/MSX/*.hpp \
$$SRC/Analyser/Static/Oric/*.hpp \
@ -221,6 +224,7 @@ HEADERS += \
$$SRC/Machines/Commodore/1540/Implementation/*.hpp \
$$SRC/Machines/Commodore/Vic-20/*.hpp \
$$SRC/Machines/Electron/*.hpp \
$$SRC/Machines/Enterprise/*.hpp \
$$SRC/Machines/MasterSystem/*.hpp \
$$SRC/Machines/MSX/*.hpp \
$$SRC/Machines/Oric/*.hpp \

View File

@ -953,6 +953,7 @@ void MainWindow::setButtonPressed(int index, bool isPressed) {
#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"
@ -973,6 +974,7 @@ void MainWindow::startMachine() {
TEST(amstradCPC);
TEST(atariST);
TEST(electron);
TEST(enterprise);
TEST(macintosh);
TEST(msx);
TEST(oric);
@ -1057,6 +1059,38 @@ void MainWindow::start_electron() {
launchTarget(std::move(target));
}
void MainWindow::start_enterprise() {
using Target = Analyser::Static::Enterprise::Target;
auto target = std::make_unique<Target>();
switch(ui->enterpriseModelComboBox->currentIndex()) {
default: target->model = Target::Model::Enterprise64; break;
case 1: target->model = Target::Model::Enterprise128; break;
case 2: target->model = Target::Model::Enterprise256; break;
}
switch(ui->enterpriseEXOSComboBox->currentIndex()) {
default: target->exos_version = Target::EXOSVersion::v10; break;
case 1: target->exos_version = Target::EXOSVersion::v20; break;
case 2: target->exos_version = Target::EXOSVersion::v21; break;
case 3: target->exos_version = Target::EXOSVersion::v23; break;
}
switch(ui->enterpriseBASICComboBox->currentIndex()) {
default: target->basic_version = Target::BASICVersion::None; break;
case 1: target->basic_version = Target::BASICVersion::v10; break;
case 2: target->basic_version = Target::BASICVersion::v11; break;
case 3: target->basic_version = Target::BASICVersion::v21; break;
}
switch(ui->enterpriseDOSComboBox->currentIndex()) {
default: target->dos = Target::DOS::None; break;
case 1: target->dos = Target::DOS::EXDOS; break;
}
launchTarget(std::move(target));
}
void MainWindow::start_macintosh() {
using Target = Analyser::Static::Macintosh::Target;
auto target = std::make_unique<Target>();
@ -1212,6 +1246,12 @@ void MainWindow::launchTarget(std::unique_ptr<Analyser::Static::Target> &&target
CheckBox(electronAP6CheckBox, "electron.hasAP6"); \
CheckBox(electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM"); \
\
/* Enterprise. */ \
ComboBox(enterpriseModelComboBox, "enterprise.model"); \
ComboBox(enterpriseEXOSComboBox, "enterprise.exos"); \
ComboBox(enterpriseBASICComboBox, "enterprise.basic"); \
ComboBox(enterpriseDOSComboBox, "enterprise.dos"); \
\
/* Macintosh. */ \
ComboBox(macintoshModelComboBox, "macintosh.model"); \
\

View File

@ -91,6 +91,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
void start_amstradCPC();
void start_atariST();
void start_electron();
void start_enterprise();
void start_macintosh();
void start_msx();
void start_oric();

View File

@ -310,6 +310,138 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="enterpriseTab">
<attribute name="title">
<string>Enterprise</string>
</attribute>
<layout class="QVBoxLayout" name="enterpriseLayout">
<item>
<layout class="QHBoxLayout" name="enterpriseHorizontalLayout">
<item>
<layout class="QFormLayout" name="enterpriseFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="enterpriseModelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="enterpriseModelComboBox">
<item>
<property name="text">
<string>Enterprise 64</string>
</property>
</item>
<item>
<property name="text">
<string>Enterprise 128</string>
</property>
</item>
<item>
<property name="text">
<string>Enterprise 256</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="enterpriseEXOSLabel">
<property name="text">
<string>EXOS:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="enterpriseEXOSComboBox">
<item>
<property name="text">
<string>Version 1.0</string>
</property>
</item>
<item>
<property name="text">
<string>Version 2.0</string>
</property>
</item>
<item>
<property name="text">
<string>Version 2.1</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="enterpriseBASICLabel">
<property name="text">
<string>BASIC:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="enterpriseBASICComboBox">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Version 1.0</string>
</property>
</item>
<item>
<property name="text">
<string>Version 1.1</string>
</property>
</item>
<item>
<property name="text">
<string>Version 2.1</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="enterpriseDOSLabel">
<property name="text">
<string>DOS:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="enterpriseDOSComboBox">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>EXDOS</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="enterpriseHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="macintoshTab">
<attribute name="title">
<string>Macintosh</string>
@ -533,75 +665,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumTab">
<attribute name="title">
<string>Spectrum</string>
</attribute>
<layout class="QVBoxLayout" name="spectrumLayout">
<item>
<layout class="QHBoxLayout" name="spectrumHorizontalLayout">
<item>
<layout class="QFormLayout" name="spectrumFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="spectrumModelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="spectrumModelComboBox">
<item>
<property name="text">
<string>16kb</string>
</property>
</item>
<item>
<property name="text">
<string>48kb</string>
</property>
</item>
<item>
<property name="text">
<string>128kb</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
<item>
<property name="text">
<string>+2a</string>
</property>
</item>
<item>
<property name="text">
<string>+3</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="spectrumHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="vic20Tab">
<attribute name="title">
<string>Vic-20</string>
@ -830,6 +893,75 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumTab">
<attribute name="title">
<string>ZX Spectrum</string>
</attribute>
<layout class="QVBoxLayout" name="spectrumLayout">
<item>
<layout class="QHBoxLayout" name="spectrumHorizontalLayout">
<item>
<layout class="QFormLayout" name="spectrumFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="spectrumModelLabel">
<property name="text">
<string>Model:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="spectrumModelComboBox">
<item>
<property name="text">
<string>16kb</string>
</property>
</item>
<item>
<property name="text">
<string>48kb</string>
</property>
</item>
<item>
<property name="text">
<string>128kb</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
<item>
<property name="text">
<string>+2a</string>
</property>
</item>
<item>
<property name="text">
<string>+3</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="spectrumHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item alignment="Qt::AlignBottom">

View File

@ -31,6 +31,7 @@ SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/DiskII/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Enterprise/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Macintosh/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp')
@ -79,6 +80,7 @@ SOURCES += glob.glob('../../Machines/Commodore/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp')
SOURCES += glob.glob('../../Machines/Electron/*.cpp')
SOURCES += glob.glob('../../Machines/Enterprise/*.cpp')
SOURCES += glob.glob('../../Machines/MasterSystem/*.cpp')
SOURCES += glob.glob('../../Machines/MSX/*.cpp')
SOURCES += glob.glob('../../Machines/Oric/*.cpp')

View File

@ -832,8 +832,11 @@ int main(int argc, char *argv[]) {
} else if(volume < 0.0 || volume > 1.0) {
std::cerr << "Cannot run with volume " << volume_string << "; volumes must be between 0.0 and 1.0." << std::endl;
} else {
const auto speaker = machine->audio_producer()->get_speaker();
if(speaker) speaker->set_output_volume(volume);
const auto audio_producer = machine->audio_producer();
if(audio_producer) {
const auto speaker = machine->audio_producer()->get_speaker();
if(speaker) speaker->set_output_volume(volume);
}
}
}
}
@ -907,26 +910,29 @@ int main(int argc, char *argv[]) {
machine->scan_producer()->set_scan_target(&scan_target);
// For now, lie about audio output intentions.
auto speaker = machine->audio_producer()->get_speaker();
if(speaker) {
// Create an audio pipe.
SDL_AudioSpec desired_audio_spec;
SDL_AudioSpec obtained_audio_spec;
const auto audio_producer = machine->audio_producer();
if(audio_producer) {
auto speaker = audio_producer->get_speaker();
if(speaker) {
// Create an audio pipe.
SDL_AudioSpec desired_audio_spec;
SDL_AudioSpec obtained_audio_spec;
SDL_zero(desired_audio_spec);
desired_audio_spec.freq = 48000; // TODO: how can I get SDL to reveal the output rate of this machine?
desired_audio_spec.format = AUDIO_S16;
desired_audio_spec.channels = 1 + int(speaker->get_is_stereo());
desired_audio_spec.samples = Uint16(SpeakerDelegate::buffered_samples);
desired_audio_spec.callback = SpeakerDelegate::SDL_audio_callback;
desired_audio_spec.userdata = &speaker_delegate;
SDL_zero(desired_audio_spec);
desired_audio_spec.freq = 48000; // TODO: how can I get SDL to reveal the output rate of this machine?
desired_audio_spec.format = AUDIO_S16;
desired_audio_spec.channels = 1 + int(speaker->get_is_stereo());
desired_audio_spec.samples = Uint16(SpeakerDelegate::buffered_samples);
desired_audio_spec.callback = SpeakerDelegate::SDL_audio_callback;
desired_audio_spec.userdata = &speaker_delegate;
speaker_delegate.audio_device = SDL_OpenAudioDevice(nullptr, 0, &desired_audio_spec, &obtained_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
speaker_delegate.audio_device = SDL_OpenAudioDevice(nullptr, 0, &desired_audio_spec, &obtained_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
speaker->set_output_rate(obtained_audio_spec.freq, desired_audio_spec.samples, obtained_audio_spec.channels == 2);
speaker_delegate.is_stereo = obtained_audio_spec.channels == 2;
speaker->set_delegate(&speaker_delegate);
SDL_PauseAudioDevice(speaker_delegate.audio_device, 0);
speaker->set_output_rate(obtained_audio_spec.freq, desired_audio_spec.samples, obtained_audio_spec.channels == 2);
speaker_delegate.is_stereo = obtained_audio_spec.channels == 2;
speaker->set_delegate(&speaker_delegate);
SDL_PauseAudioDevice(speaker_delegate.audio_device, 0);
}
}
/*

View File

@ -145,6 +145,12 @@ struct PartialMachineCycle {
forceinline bool is_wait() const {
return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait;
}
/*!
@returns @c true if this partial machine cycle is a memory access; @c false otherwise.
*/
forceinline bool is_memory_access() const {
return operation <= Operation::Write;
}
enum Line {
CLK = 1 << 0,

View File

@ -0,0 +1,5 @@
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
exos.rom

View File

@ -49,8 +49,10 @@ class Controller:
void run_for(const Cycles cycles);
/*!
Sets the current drive(s). Normally this will be exactly one, but some machines allow
zero or multiple drives to be attached, with useless results.
Sets the current drive(s), by bit mask. Normally this will be exactly one, but some
machines allow zero or multiple drives to be attached, with useless results.
E.g. supply 1 to select drive 0, 2 to select drive 1, 4 to select drive 2, etc.
*/
void set_drive(int index_mask);

View File

@ -0,0 +1,57 @@
//
// FAT12.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "FAT12.hpp"
#include "Utility/ImplicitSectors.hpp"
using namespace Storage::Disk;
FAT12::FAT12(const std::string &file_name) :
MFMSectorDump(file_name) {
// The only sanity check here is whether a sensible
// geometry is encoded in the first sector, or can be guessed.
off_t file_size = file_.stats().st_size;
if(file_size < 512) throw Error::InvalidFormat;
// Inspect the FAT.
file_.seek(11, SEEK_SET);
sector_size_ = file_.get16le();
file_.seek(19, SEEK_SET);
const uint16_t total_sectors = file_.get16le();
file_.seek(24, SEEK_SET);
sector_count_ = file_.get16le();
head_count_ = file_.get16le();
// Throw if there would seemingly be an incomplete track.
if(file_size != total_sectors*sector_size_) throw Error::InvalidFormat;
if(total_sectors % (head_count_ * sector_count_)) throw Error::InvalidFormat;
track_count_ = int(total_sectors / (head_count_ * sector_count_));
// Check that there is a valid power-of-two sector size.
uint8_t log_sector_size = 2;
while(log_sector_size < 5 && (1 << (7+log_sector_size)) != sector_size_) {
++log_sector_size;
}
if(log_sector_size >= 5) throw Error::InvalidFormat;
set_geometry(sector_count_, log_sector_size, 1, true);
}
HeadPosition FAT12::get_maximum_head_position() {
return HeadPosition(track_count_);
}
int FAT12::get_head_count() {
return head_count_;
}
long FAT12::get_file_offset_for_position(Track::Address address) {
return (address.position.as_int()*head_count_ + address.head) * sector_size_ * sector_count_;
}

View File

@ -1,5 +1,5 @@
//
// MSXDSK.hpp
// FAT12.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
@ -17,12 +17,12 @@ namespace Storage {
namespace Disk {
/*!
Provides a @c DiskImage descriging an MSX-style disk image:
Provides a @c DiskImage holding an MSDOS-style FAT12 disk image:
a sector dump of appropriate proportions.
*/
class MSXDSK: public MFMSectorDump {
class FAT12: public MFMSectorDump {
public:
MSXDSK(const std::string &file_name);
FAT12(const std::string &file_name);
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
@ -31,6 +31,8 @@ class MSXDSK: public MFMSectorDump {
int head_count_;
int track_count_;
int sector_count_;
int sector_size_;
};
}

View File

@ -1,59 +0,0 @@
//
// MSXDSK.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MSXDSK.hpp"
#include "Utility/ImplicitSectors.hpp"
namespace {
constexpr int sectors_per_track = 9;
constexpr int sector_size = 2;
constexpr off_t track_size = (128 << sector_size)*sectors_per_track;
}
using namespace Storage::Disk;
MSXDSK::MSXDSK(const std::string &file_name) :
MFMSectorDump(file_name) {
// The only sanity check here is whether a sensible
// geometry can be guessed.
off_t file_size = file_.stats().st_size;
// Throw if there would seemingly be an incomplete track.
if(file_size % track_size) throw Error::InvalidFormat;
track_count_ = int(file_size / track_size);
head_count_ = 1;
// Throw if too large or too small or too large for single sided and
// clearly not double sided.
if(track_count_ < 40) throw Error::InvalidFormat;
if(track_count_ > 82*2) throw Error::InvalidFormat;
if(track_count_ > 82 && track_count_&1) throw Error::InvalidFormat;
// The below effectively prefers the idea of a single-sided 80-track disk
// to a double-sided 40-track disk. Emulators have to guess.
if(track_count_ > 82) {
track_count_ /= 2;
head_count_ = 2;
}
set_geometry(sectors_per_track, sector_size, 1, true);
}
HeadPosition MSXDSK::get_maximum_head_position() {
return HeadPosition(track_count_);
}
int MSXDSK::get_head_count() {
return head_count_;
}
long MSXDSK::get_file_offset_for_position(Track::Address address) {
return (address.position.as_int()*head_count_ + address.head) * 512 * 9;
}

View File

@ -29,13 +29,14 @@ enum Type: IntType {
Coleco = 1 << 10,
Commodore = 1 << 11,
DiskII = 1 << 12,
Sega = 1 << 13,
Macintosh = 1 << 14,
MSX = 1 << 15,
Oric = 1 << 16,
ZX80 = 1 << 17,
ZX81 = 1 << 18,
ZXSpectrum = 1 << 19,
Enterprise = 1 << 13,
Sega = 1 << 14,
Macintosh = 1 << 15,
MSX = 1 << 16,
Oric = 1 << 17,
ZX80 = 1 << 18,
ZX81 = 1 << 19,
ZXSpectrum = 1 << 20,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,