diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp index b04064020..b1b21eab8 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp @@ -27,18 +27,18 @@ private: std::vector machines_; class MultiKeyboard: public Inputs::Keyboard { - public: - MultiKeyboard(const std::vector &); + public: + MultiKeyboard(const std::vector &); - bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final; - void reset_all_keys() final; - const std::set &observed_keys() const final; - bool is_exclusive() const final; + bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final; + void reset_all_keys() final; + const std::set &observed_keys() const final; + bool is_exclusive() const final; - private: - const std::vector &machines_; - std::set observed_keys_; - bool is_exclusive_ = false; + private: + const std::vector &machines_; + std::set observed_keys_; + bool is_exclusive_ = false; }; std::unique_ptr keyboard_; diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index 5cc945700..2d7693575 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -285,14 +285,14 @@ private: automatically to gain run_for(HalfCycles). */ template class HalfClockReceiver: public T { - public: - using T::T; +public: + using T::T; - forceinline void run_for(const HalfCycles half_cycles) { - half_cycles_ += half_cycles; - T::run_for(half_cycles_.flush()); - } + forceinline void run_for(const HalfCycles half_cycles) { + half_cycles_ += half_cycles; + T::run_for(half_cycles_.flush()); + } - private: - HalfCycles half_cycles_; +private: + HalfCycles half_cycles_; }; diff --git a/ClockReceiver/DeferredQueue.hpp b/ClockReceiver/DeferredQueue.hpp index f303c9ceb..0263aab0b 100644 --- a/ClockReceiver/DeferredQueue.hpp +++ b/ClockReceiver/DeferredQueue.hpp @@ -98,28 +98,28 @@ private: This list is efficient only for short queues. */ template class DeferredQueuePerformer: public DeferredQueue { - public: - /// Constructs a DeferredQueue that will call target(period) in between deferred actions. - constexpr DeferredQueuePerformer(std::function &&target) : target_(std::move(target)) {} +public: + /// Constructs a DeferredQueue that will call target(period) in between deferred actions. + constexpr DeferredQueuePerformer(std::function &&target) : target_(std::move(target)) {} - /*! - Runs for @c length units of time. + /*! + Runs for @c length units of time. - The constructor-supplied target will be called with one or more periods that add up to @c length; - any scheduled actions will be called between periods. - */ - void run_for(TimeUnit length) { - auto time_to_next = DeferredQueue::time_until_next_action(); - while(time_to_next != TimeUnit(-1) && time_to_next <= length) { - target_(time_to_next); - length -= time_to_next; - DeferredQueue::advance(time_to_next); - } - - DeferredQueue::advance(length); - target_(length); + The constructor-supplied target will be called with one or more periods that add up to @c length; + any scheduled actions will be called between periods. + */ + void run_for(TimeUnit length) { + auto time_to_next = DeferredQueue::time_until_next_action(); + while(time_to_next != TimeUnit(-1) && time_to_next <= length) { + target_(time_to_next); + length -= time_to_next; + DeferredQueue::advance(time_to_next); } - private: - std::function target_; + DeferredQueue::advance(length); + target_(length); + } + +private: + std::function target_; }; diff --git a/ClockReceiver/VSyncPredictor.hpp b/ClockReceiver/VSyncPredictor.hpp index 74e7d1176..5ae457e98 100644 --- a/ClockReceiver/VSyncPredictor.hpp +++ b/ClockReceiver/VSyncPredictor.hpp @@ -104,40 +104,40 @@ public: private: class VarianceCollector { - public: - VarianceCollector(Time::Nanos default_value) { - sum_ = default_value * 128; - for(int c = 0; c < 128; ++c) { - history_[c] = default_value; - } + public: + VarianceCollector(Time::Nanos default_value) { + sum_ = default_value * 128; + for(int c = 0; c < 128; ++c) { + history_[c] = default_value; } + } - void post(Time::Nanos value) { - sum_ -= history_[write_pointer_]; - sum_ += value; - history_[write_pointer_] = value; - write_pointer_ = (write_pointer_ + 1) & 127; + void post(Time::Nanos value) { + sum_ -= history_[write_pointer_]; + sum_ += value; + history_[write_pointer_] = value; + write_pointer_ = (write_pointer_ + 1) & 127; + } + + Time::Nanos mean() { + return sum_ / 128; + } + + Time::Nanos variance() { + // I haven't yet come up with a better solution that calculating this + // in whole every time, given the way that the mean mutates. + Time::Nanos variance = 0; + for(int c = 0; c < 128; ++c) { + const auto difference = ((history_[c] * 128) - sum_) / 128; + variance += (difference * difference); } + return variance / 128; + } - Time::Nanos mean() { - return sum_ / 128; - } - - Time::Nanos variance() { - // I haven't yet come up with a better solution that calculating this - // in whole every time, given the way that the mean mutates. - Time::Nanos variance = 0; - for(int c = 0; c < 128; ++c) { - const auto difference = ((history_[c] * 128) - sum_) / 128; - variance += (difference * difference); - } - return variance / 128; - } - - private: - Time::Nanos sum_; - Time::Nanos history_[128]; - size_t write_pointer_ = 0; + private: + Time::Nanos sum_; + Time::Nanos history_[128]; + size_t write_pointer_ = 0; }; Nanos redraw_begin_time_ = 0; diff --git a/Components/8530/z8530.hpp b/Components/8530/z8530.hpp index 63e4bcf6d..ef6d64822 100644 --- a/Components/8530/z8530.hpp +++ b/Components/8530/z8530.hpp @@ -64,36 +64,36 @@ public: private: class Channel { - public: - uint8_t read(bool data, uint8_t pointer); - void write(bool data, uint8_t pointer, uint8_t value); - void set_dcd(bool level); - bool get_interrupt_line() const; + public: + uint8_t read(bool data, uint8_t pointer); + void write(bool data, uint8_t pointer, uint8_t value); + void set_dcd(bool level); + bool get_interrupt_line() const; - private: - uint8_t data_ = 0xff; + private: + uint8_t data_ = 0xff; - enum class Parity { - Even, Odd, Off - } parity_ = Parity::Off; + enum class Parity { + Even, Odd, Off + } parity_ = Parity::Off; - enum class StopBits { - Synchronous, OneBit, OneAndAHalfBits, TwoBits - } stop_bits_ = StopBits::Synchronous; + enum class StopBits { + Synchronous, OneBit, OneAndAHalfBits, TwoBits + } stop_bits_ = StopBits::Synchronous; - enum class Sync { - Monosync, Bisync, SDLC, External - } sync_mode_ = Sync::Monosync; + enum class Sync { + Monosync, Bisync, SDLC, External + } sync_mode_ = Sync::Monosync; - int clock_rate_multiplier_ = 1; + int clock_rate_multiplier_ = 1; - uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1. + uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1. - uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf. - bool external_status_interrupt_ = false; - uint8_t external_interrupt_status_ = 0; + uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf. + bool external_status_interrupt_ = false; + uint8_t external_interrupt_status_ = 0; - bool dcd_ = false; + bool dcd_ = false; } channels_[2]; uint8_t pointer_ = 0; diff --git a/Components/9918/Implementation/Fetch.hpp b/Components/9918/Implementation/Fetch.hpp index 38bf0720d..c84c0368e 100644 --- a/Components/9918/Implementation/Fetch.hpp +++ b/Components/9918/Implementation/Fetch.hpp @@ -192,111 +192,111 @@ constexpr SpriteMode sprite_mode(ScreenMode screen_mode) { // TODO: should this be extended to include Master System sprites? template class SpriteFetcher { - public: - using AddressT = typename Base::AddressT; +public: + using AddressT = typename Base::AddressT; - // The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour - // table, which is intended to fill the 512 bytes before the programmer-located sprite - // attribute table. - // - // It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of - // the attribute table, and forcing them to 1 but masking out bit 9 for the colour table. - // - // AttributeAddressMask is used to enable or disable that behaviour. - static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000); + // The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour + // table, which is intended to fill the 512 bytes before the programmer-located sprite + // attribute table. + // + // It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of + // the attribute table, and forcing them to 1 but masking out bit 9 for the colour table. + // + // AttributeAddressMask is used to enable or disable that behaviour. + static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000); - SpriteFetcher(Base *base, uint8_t y) : - base(base), - y(y) {} + SpriteFetcher(Base *base, uint8_t y) : + base(base), + y(y) {} - void fetch_location(int slot) { - fetch_xy(slot); + void fetch_location(int slot) { + fetch_xy(slot); - if constexpr (mode == SpriteMode::Mode2) { - fetch_xy(slot + 1); + if constexpr (mode == SpriteMode::Mode2) { + fetch_xy(slot + 1); - base->name_[0] = name(slot); - base->name_[1] = name(slot + 1); - } + base->name_[0] = name(slot); + base->name_[1] = name(slot + 1); } + } - void fetch_pattern(int slot) { - switch(mode) { - case SpriteMode::Mode1: - fetch_image(slot, name(slot)); - break; + void fetch_pattern(int slot) { + switch(mode) { + case SpriteMode::Mode1: + fetch_image(slot, name(slot)); + break; - case SpriteMode::Mode2: - fetch_image(slot, base->name_[0]); - fetch_image(slot + 1, base->name_[1]); - break; - } + case SpriteMode::Mode2: + fetch_image(slot, base->name_[0]); + fetch_image(slot + 1, base->name_[1]); + break; } + } - void fetch_y(int sprite) { - const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2)); - const uint8_t sprite_y = base->ram_[address]; - base->posit_sprite(sprite, sprite_y, y); - } + void fetch_y(int sprite) { + const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2)); + const uint8_t sprite_y = base->ram_[address]; + base->posit_sprite(sprite, sprite_y, y); + } - private: - void fetch_xy(int slot) { - auto &buffer = *base->fetch_sprite_buffer_; - buffer.active_sprites[slot].x = - base->ram_[ - base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1)) +private: + void fetch_xy(int slot) { + auto &buffer = *base->fetch_sprite_buffer_; + buffer.active_sprites[slot].x = + base->ram_[ + base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1)) + ]; + } + + uint8_t name(int slot) { + auto &buffer = *base->fetch_sprite_buffer_; + const AddressT address = + base->sprite_attribute_table_address_ & + AttributeAddressMask & + bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2)); + const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0); + return name; + } + + void fetch_image(int slot, uint8_t name) { + uint8_t colour = 0; + auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot]; + switch(mode) { + case SpriteMode::Mode1: + // Fetch colour from the attribute table, per this sprite's slot. + colour = base->ram_[ + base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3)) ]; + break; + + case SpriteMode::Mode2: { + // Fetch colour from the colour table, per this sprite's slot and row. + const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200); + colour = base->ram_[ + colour_table_address & + bits<9>( + AddressT(sprite.index << 4) | + AddressT(sprite.row) + ) + ]; + } break; } + sprite.image[2] = colour; + sprite.x -= sprite.early_clock(); - uint8_t name(int slot) { - auto &buffer = *base->fetch_sprite_buffer_; - const AddressT address = - base->sprite_attribute_table_address_ & - AttributeAddressMask & - bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2)); - const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0); - return name; - } + const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row)); + sprite.image[0] = base->ram_[graphic_location]; + sprite.image[1] = base->ram_[graphic_location+16]; - void fetch_image(int slot, uint8_t name) { - uint8_t colour = 0; - auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot]; - switch(mode) { - case SpriteMode::Mode1: - // Fetch colour from the attribute table, per this sprite's slot. - colour = base->ram_[ - base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3)) - ]; - break; - - case SpriteMode::Mode2: { - // Fetch colour from the colour table, per this sprite's slot and row. - const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200); - colour = base->ram_[ - colour_table_address & - bits<9>( - AddressT(sprite.index << 4) | - AddressT(sprite.row) - ) - ]; - } break; - } - sprite.image[2] = colour; - sprite.x -= sprite.early_clock(); - - const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row)); - sprite.image[0] = base->ram_[graphic_location]; - sprite.image[1] = base->ram_[graphic_location+16]; - - if constexpr (SpriteBuffer::test_is_filling) { - if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) { - base->fetch_sprite_buffer_->is_filling = false; - } + if constexpr (SpriteBuffer::test_is_filling) { + if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) { + base->fetch_sprite_buffer_->is_filling = false; } } + } - Base *const base; - const uint8_t y; + Base *const base; + const uint8_t y; }; template diff --git a/Components/9918/Implementation/Storage.hpp b/Components/9918/Implementation/Storage.hpp index 186e597bb..b89df8e27 100644 --- a/Components/9918/Implementation/Storage.hpp +++ b/Components/9918/Implementation/Storage.hpp @@ -439,12 +439,12 @@ struct Storage>: } } - private: - static constexpr auto refresh_events = events(); - static constexpr auto no_sprites_events = events>(); - static constexpr auto sprites_events = events>(); - static constexpr auto text_events = events(); - static constexpr auto character_events = events(); +private: + static constexpr auto refresh_events = events(); + static constexpr auto no_sprites_events = events>(); + static constexpr auto sprites_events = events>(); + static constexpr auto text_events = events(); + static constexpr auto character_events = events(); }; // Master System-specific storage. diff --git a/Components/OPx/OPLL.hpp b/Components/OPx/OPLL.hpp index bd7e85c8d..1b8c70e2c 100644 --- a/Components/OPx/OPLL.hpp +++ b/Components/OPx/OPLL.hpp @@ -20,109 +20,109 @@ namespace Yamaha::OPL { class OPLL: public OPLBase { - public: - /// Creates a new OPLL or VRC7. - OPLL(Concurrency::AsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false); +public: + /// Creates a new OPLL or VRC7. + OPLL(Concurrency::AsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false); - /// As per ::SampleSource; provides audio output. - template - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *); - void set_sample_volume_range(std::int16_t range); - bool is_zero_level() const { return false; } // TODO. + /// As per ::SampleSource; provides audio output. + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *); + void set_sample_volume_range(std::int16_t range); + bool is_zero_level() const { return false; } // TODO. - // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in - // rhythm mode, but it's correct for melodic output. - double average_output_peak() const { return 0.5; } + // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in + // rhythm mode, but it's correct for melodic output. + double average_output_peak() const { return 0.5; } - /// Reads from the OPL. - uint8_t read(uint16_t address); + /// Reads from the OPL. + uint8_t read(uint16_t address); - private: - friend OPLBase; - void write_register(uint8_t address, uint8_t value); +private: + friend OPLBase; + void write_register(uint8_t address, uint8_t value); - int audio_divider_ = 0; - int audio_offset_ = 0; - std::atomic total_volume_; + int audio_divider_ = 0; + int audio_offset_ = 0; + std::atomic total_volume_; - int16_t output_levels_[18]; - void update_all_channels(); + int16_t output_levels_[18]; + void update_all_channels(); - int melodic_output(int channel); - int bass_drum() const; - int tom_tom() const; - int snare_drum() const; - int cymbal() const; - int high_hat() const; + int melodic_output(int channel); + int bass_drum() const; + int tom_tom() const; + int snare_drum() const; + int cymbal() const; + int high_hat() const; - static constexpr int period_precision = 9; - static constexpr int envelope_precision = 7; + static constexpr int period_precision = 9; + static constexpr int envelope_precision = 7; - // Standard melodic phase and envelope generators; - // - // These are assigned as: - // - // [x], 0 <= x < 9 = carrier for channel x; - // [x+9] = modulator for channel x. - // - PhaseGenerator phase_generators_[18]; - EnvelopeGenerator envelope_generators_[18]; - KeyLevelScaler key_level_scalers_[18]; + // Standard melodic phase and envelope generators; + // + // These are assigned as: + // + // [x], 0 <= x < 9 = carrier for channel x; + // [x+9] = modulator for channel x. + // + PhaseGenerator phase_generators_[18]; + EnvelopeGenerator envelope_generators_[18]; + KeyLevelScaler key_level_scalers_[18]; - // Dedicated rhythm envelope generators and attenuations. - EnvelopeGenerator rhythm_envelope_generators_[6]; - enum RhythmIndices { - HighHat = 0, - Cymbal = 1, - TomTom = 2, - Snare = 3, - BassCarrier = 4, - BassModulator = 5 - }; + // Dedicated rhythm envelope generators and attenuations. + EnvelopeGenerator rhythm_envelope_generators_[6]; + enum RhythmIndices { + HighHat = 0, + Cymbal = 1, + TomTom = 2, + Snare = 3, + BassCarrier = 4, + BassModulator = 5 + }; - // Channel specifications. - struct Channel { - int octave = 0; - int period = 0; - int instrument = 0; + // Channel specifications. + struct Channel { + int octave = 0; + int period = 0; + int instrument = 0; - int attenuation = 0; - int modulator_attenuation = 0; + int attenuation = 0; + int modulator_attenuation = 0; - Waveform carrier_waveform = Waveform::Sine; - Waveform modulator_waveform = Waveform::Sine; + Waveform carrier_waveform = Waveform::Sine; + Waveform modulator_waveform = Waveform::Sine; - int carrier_key_rate_scale_multiplier = 0; - int modulator_key_rate_scale_multiplier = 0; + int carrier_key_rate_scale_multiplier = 0; + int modulator_key_rate_scale_multiplier = 0; - LogSign modulator_output; - int modulator_feedback = 0; + LogSign modulator_output; + int modulator_feedback = 0; - bool use_sustain = false; - } channels_[9]; + bool use_sustain = false; + } channels_[9]; - // The low-frequency oscillator. - LowFrequencyOscillator oscillator_; - bool rhythm_mode_enabled_ = false; - bool is_vrc7_ = false; + // The low-frequency oscillator. + LowFrequencyOscillator oscillator_; + bool rhythm_mode_enabled_ = false; + bool is_vrc7_ = false; - // Contains the current configuration of the custom instrument. - uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + // Contains the current configuration of the custom instrument. + uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - // Helpers to push per-channel information. + // Helpers to push per-channel information. - /// Pushes the current octave and period to channel @c channel. - void set_channel_period(int channel); + /// Pushes the current octave and period to channel @c channel. + void set_channel_period(int channel); - /// Installs the appropriate instrument on channel @c channel. - void install_instrument(int channel); + /// Installs the appropriate instrument on channel @c channel. + void install_instrument(int channel); - /// Sets whether the sustain level is used for channel @c channel based on its current instrument - /// and the user's selection. - void set_use_sustain(int channel); + /// Sets whether the sustain level is used for channel @c channel based on its current instrument + /// and the user's selection. + void set_use_sustain(int channel); - /// @returns The 8-byte definition of instrument @c instrument. - const uint8_t *instrument_definition(int instrument, int channel) const; + /// @returns The 8-byte definition of instrument @c instrument. + const uint8_t *instrument_definition(int instrument, int channel) const; }; } diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index 6e1b82b50..00cdddaa9 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -26,15 +26,15 @@ template struct TaskQueueStorage { Performer performer; - protected: - void update() { - auto time_now = Time::nanos_now(); - performer.perform(time_now - last_fired_); - last_fired_ = time_now; - } +protected: + void update() { + auto time_now = Time::nanos_now(); + performer.perform(time_now - last_fired_); + last_fired_ = time_now; + } - private: - Time::Nanos last_fired_; +private: + Time::Nanos last_fired_; }; /// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer. diff --git a/InstructionSets/ARM/Registers.hpp b/InstructionSets/ARM/Registers.hpp index 502b81f24..e9eb28191 100644 --- a/InstructionSets/ARM/Registers.hpp +++ b/InstructionSets/ARM/Registers.hpp @@ -45,344 +45,344 @@ enum class Mode { /// Appropriate prefetch offsets are left to other code to handle. /// This is to try to keep this structure independent of a specific ARM implementation. struct Registers { - public: - // Don't allow copying. - Registers(const Registers &) = delete; - Registers &operator =(const Registers &) = delete; - Registers() = default; +public: + // Don't allow copying. + Registers(const Registers &) = delete; + Registers &operator =(const Registers &) = delete; + Registers() = default; - /// Sets the N and Z flags according to the value of @c result. - void set_nz(const uint32_t value) { - zero_result_ = negative_flag_ = value; + /// Sets the N and Z flags according to the value of @c result. + void set_nz(const uint32_t value) { + zero_result_ = negative_flag_ = value; + } + + /// Sets C if @c value is non-zero; resets it otherwise. + void set_c(const uint32_t value) { + carry_flag_ = value; + } + + /// @returns @c 1 if carry is set; @c 0 otherwise. + uint32_t c() const { + return carry_flag_ ? 1 : 0; + } + + /// Sets V if the highest bit of @c value is set; resets it otherwise. + void set_v(const uint32_t value) { + overflow_flag_ = value; + } + + /// @returns The current status bits, separate from the PC — mode, NVCZ and the two interrupt flags. + uint32_t status() const { + return + uint32_t(mode_) | + (negative_flag_ & ConditionCode::Negative) | + (zero_result_ ? 0 : ConditionCode::Zero) | + (carry_flag_ ? ConditionCode::Carry : 0) | + ((overflow_flag_ >> 3) & ConditionCode::Overflow) | + interrupt_flags_; + } + + /// @returns The full PC + status bits. + uint32_t pc_status(const uint32_t offset) const { + return + ((active_[15] + offset) & ConditionCode::Address) | + status(); + } + + /// Sets status bits only, subject to mode. + void set_status(const uint32_t status) { + // ... in user mode the other flags (I, F, M1, M0) are protected from direct change + // but in non-user modes these will also be affected, accepting copies of bits 27, 26, + // 1 and 0 of the result respectively. + + negative_flag_ = status; + overflow_flag_ = status << 3; + carry_flag_ = status & ConditionCode::Carry; + zero_result_ = ~status & ConditionCode::Zero; + + if(mode_ != Mode::User) { + set_mode(Mode(status & 3)); + interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable); } + } - /// Sets C if @c value is non-zero; resets it otherwise. - void set_c(const uint32_t value) { - carry_flag_ = value; + /// @returns The current mode. + Mode mode() const { + return mode_; + } + + /// Sets a new PC. + void set_pc(const uint32_t value) { + active_[15] = value & ConditionCode::Address; + } + + /// @returns The stored PC plus @c offset limited to 26 bits. + uint32_t pc(const uint32_t offset) const { + return (active_[15] + offset) & ConditionCode::Address; + } + + // MARK: - Exceptions. + + enum class Exception { + /// Reset line went from high to low. + Reset = 0x00, + /// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found. + UndefinedInstruction = 0x04, + /// Code executed a software interrupt. + SoftwareInterrupt = 0x08, + /// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue. + PrefetchAbort = 0x0c, + /// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled + /// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM + /// otherwise complete, including pointer writeback. + DataAbort = 0x10, + /// The first data transfer attempted within an instruction was above address 0x3ff'ffff. + Address = 0x14, + /// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set. + IRQ = 0x18, + /// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set. + FIQ = 0x1c, + }; + static constexpr uint32_t pc_offset_during(const Exception exception) { + // The below is somewhat convoluted by the assumed execution model: + // + // * exceptions occuring during execution of an instruction are taken + // to occur after R15 has already been incremented by 4; but + // * exceptions occurring instead of execution of an instruction are + // taken to occur with R15 pointing to an instruction that hasn't begun. + // + // i.e. in net R15 always refers to the next instruction + // that has not yet started. + switch(exception) { + // "To return normally from FIQ use SUBS PC, R14_fiq, #4". + case Exception::FIQ: return 4; + + // "To return normally from IRQ use SUBS PC, R14_irq, #4". + case Exception::IRQ: return 4; + + // "If a return is required from [address exception traps], use + // SUBS PC, R14_svc, #4. This will return to the instruction after + // the one causing the trap". + case Exception::Address: return 4; + + // "A Data Abort requires [work before a return], the return being + // done by SUBS PC, R14_svc, #8" (and returning to the instruction + // that aborted). + case Exception::DataAbort: return 4; + + // "To continue after a Prefetch Abort use SUBS PC, R14_svc #4. + // This will attempt to re-execute the aborting instruction." + case Exception::PrefetchAbort: return 4; + + // "To return from a SWI, use MOVS PC, R14_svc. This returns to the instruction + // following the SWI". + case Exception::SoftwareInterrupt: return 0; + + // "To return from [an undefined instruction trap] use MOVS PC, R14_svc. + // This returns to the instruction following the undefined instruction". + case Exception::UndefinedInstruction: return 0; + + // Unspecified; a guess. + case Exception::Reset: return 0; } + return 4; + } - /// @returns @c 1 if carry is set; @c 0 otherwise. - uint32_t c() const { - return carry_flag_ ? 1 : 0; + /// Updates the program counter, interupt flags and link register if applicable to begin @c exception. + template + void exception() { + const auto r14 = pc_status(pc_offset_during(type)); + switch(type) { + case Exception::IRQ: set_mode(Mode::IRQ); break; + case Exception::FIQ: set_mode(Mode::FIQ); break; + default: set_mode(Mode::Supervisor); break; } + active_[14] = r14; - /// Sets V if the highest bit of @c value is set; resets it otherwise. - void set_v(const uint32_t value) { - overflow_flag_ = value; + interrupt_flags_ |= ConditionCode::IRQDisable; + if constexpr (type == Exception::Reset || type == Exception::FIQ) { + interrupt_flags_ |= ConditionCode::FIQDisable; } + set_pc(uint32_t(type)); + } - /// @returns The current status bits, separate from the PC — mode, NVCZ and the two interrupt flags. - uint32_t status() const { - return - uint32_t(mode_) | - (negative_flag_ & ConditionCode::Negative) | - (zero_result_ ? 0 : ConditionCode::Zero) | - (carry_flag_ ? ConditionCode::Carry : 0) | - ((overflow_flag_ >> 3) & ConditionCode::Overflow) | - interrupt_flags_; + /// Returns @c true if: (i) the exception type is IRQ or FIQ; and (ii) the processor is currently accepting such interrupts. + /// Otherwise returns @c false. + template + bool would_interrupt() { + switch(type) { + case Exception::IRQ: + if(interrupt_flags_ & ConditionCode::IRQDisable) { + return false; + } + break; + + case Exception::FIQ: + if(interrupt_flags_ & ConditionCode::FIQDisable) { + return false; + } + break; + + default: return false; } + return true; + } - /// @returns The full PC + status bits. - uint32_t pc_status(const uint32_t offset) const { - return - ((active_[15] + offset) & ConditionCode::Address) | - status(); - } + // MARK: - Condition tests. - /// Sets status bits only, subject to mode. - void set_status(const uint32_t status) { - // ... in user mode the other flags (I, F, M1, M0) are protected from direct change - // but in non-user modes these will also be affected, accepting copies of bits 27, 26, - // 1 and 0 of the result respectively. - - negative_flag_ = status; - overflow_flag_ = status << 3; - carry_flag_ = status & ConditionCode::Carry; - zero_result_ = ~status & ConditionCode::Zero; - - if(mode_ != Mode::User) { - set_mode(Mode(status & 3)); - interrupt_flags_ = status & (ConditionCode::IRQDisable | ConditionCode::FIQDisable); - } - } - - /// @returns The current mode. - Mode mode() const { - return mode_; - } - - /// Sets a new PC. - void set_pc(const uint32_t value) { - active_[15] = value & ConditionCode::Address; - } - - /// @returns The stored PC plus @c offset limited to 26 bits. - uint32_t pc(const uint32_t offset) const { - return (active_[15] + offset) & ConditionCode::Address; - } - - // MARK: - Exceptions. - - enum class Exception { - /// Reset line went from high to low. - Reset = 0x00, - /// Either an undefined instruction or a coprocessor instruction for which no coprocessor was found. - UndefinedInstruction = 0x04, - /// Code executed a software interrupt. - SoftwareInterrupt = 0x08, - /// The memory subsystem indicated an abort during prefetch and that instruction has now come to the head of the queue. - PrefetchAbort = 0x0c, - /// The memory subsystem indicated an abort during an instruction; if it is an LDR or STR then this should be signalled - /// before any instruction execution. If it was an LDM then loading stops upon a data abort but both an LDM and STM - /// otherwise complete, including pointer writeback. - DataAbort = 0x10, - /// The first data transfer attempted within an instruction was above address 0x3ff'ffff. - Address = 0x14, - /// The IRQ line was low at the end of an instruction and ConditionCode::IRQDisable was not set. - IRQ = 0x18, - /// The FIQ went low at least one cycle ago and ConditionCode::FIQDisable was not set. - FIQ = 0x1c, + /// @returns @c true if @c condition tests as true; @c false otherwise. + bool test(const Condition condition) const { + const auto ne = [&]() -> bool { + return zero_result_; + }; + const auto cs = [&]() -> bool { + return carry_flag_; + }; + const auto mi = [&]() -> bool { + return negative_flag_ & ConditionCode::Negative; + }; + const auto vs = [&]() -> bool { + return overflow_flag_ & ConditionCode::Negative; + }; + const auto hi = [&]() -> bool { + return carry_flag_ && zero_result_; + }; + const auto lt = [&]() -> bool { + return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative; + }; + const auto le = [&]() -> bool { + return !zero_result_ || lt(); }; - static constexpr uint32_t pc_offset_during(const Exception exception) { - // The below is somewhat convoluted by the assumed execution model: - // - // * exceptions occuring during execution of an instruction are taken - // to occur after R15 has already been incremented by 4; but - // * exceptions occurring instead of execution of an instruction are - // taken to occur with R15 pointing to an instruction that hasn't begun. - // - // i.e. in net R15 always refers to the next instruction - // that has not yet started. - switch(exception) { - // "To return normally from FIQ use SUBS PC, R14_fiq, #4". - case Exception::FIQ: return 4; - // "To return normally from IRQ use SUBS PC, R14_irq, #4". - case Exception::IRQ: return 4; + switch(condition) { + case Condition::EQ: return !ne(); + case Condition::NE: return ne(); + case Condition::CS: return cs(); + case Condition::CC: return !cs(); + case Condition::MI: return mi(); + case Condition::PL: return !mi(); + case Condition::VS: return vs(); + case Condition::VC: return !vs(); - // "If a return is required from [address exception traps], use - // SUBS PC, R14_svc, #4. This will return to the instruction after - // the one causing the trap". - case Exception::Address: return 4; + case Condition::HI: return hi(); + case Condition::LS: return !hi(); + case Condition::GE: return !lt(); + case Condition::LT: return lt(); + case Condition::GT: return !le(); + case Condition::LE: return le(); - // "A Data Abort requires [work before a return], the return being - // done by SUBS PC, R14_svc, #8" (and returning to the instruction - // that aborted). - case Exception::DataAbort: return 4; - - // "To continue after a Prefetch Abort use SUBS PC, R14_svc #4. - // This will attempt to re-execute the aborting instruction." - case Exception::PrefetchAbort: return 4; - - // "To return from a SWI, use MOVS PC, R14_svc. This returns to the instruction - // following the SWI". - case Exception::SoftwareInterrupt: return 0; - - // "To return from [an undefined instruction trap] use MOVS PC, R14_svc. - // This returns to the instruction following the undefined instruction". - case Exception::UndefinedInstruction: return 0; - - // Unspecified; a guess. - case Exception::Reset: return 0; - } - return 4; + case Condition::AL: return true; + case Condition::NV: return false; } - /// Updates the program counter, interupt flags and link register if applicable to begin @c exception. - template - void exception() { - const auto r14 = pc_status(pc_offset_during(type)); - switch(type) { - case Exception::IRQ: set_mode(Mode::IRQ); break; - case Exception::FIQ: set_mode(Mode::FIQ); break; - default: set_mode(Mode::Supervisor); break; - } - active_[14] = r14; + return false; + } - interrupt_flags_ |= ConditionCode::IRQDisable; - if constexpr (type == Exception::Reset || type == Exception::FIQ) { - interrupt_flags_ |= ConditionCode::FIQDisable; - } - set_pc(uint32_t(type)); + /// Sets current execution mode. + void set_mode(const Mode target_mode) { + if(mode_ == target_mode) { + return; } - /// Returns @c true if: (i) the exception type is IRQ or FIQ; and (ii) the processor is currently accepting such interrupts. - /// Otherwise returns @c false. - template - bool would_interrupt() { - switch(type) { - case Exception::IRQ: - if(interrupt_flags_ & ConditionCode::IRQDisable) { - return false; - } - break; + // For outgoing modes other than FIQ, only save the final two registers for now; + // if the incoming mode is FIQ then the other five will be saved in the next switch. + // For FIQ, save all seven up front. + switch(mode_) { + // FIQ outgoing: save R8 to R14. + case Mode::FIQ: + std::copy(active_.begin() + 8, active_.begin() + 15, fiq_registers_.begin()); + break; - case Exception::FIQ: - if(interrupt_flags_ & ConditionCode::FIQDisable) { - return false; - } - break; - - default: return false; - } - return true; + // Non-FIQ outgoing: save R13 and R14. If saving to the user registers, + // use only the final two slots. + case Mode::User: + std::copy(active_.begin() + 13, active_.begin() + 15, user_registers_.begin() + 5); + break; + case Mode::Supervisor: + std::copy(active_.begin() + 13, active_.begin() + 15, supervisor_registers_.begin()); + break; + case Mode::IRQ: + std::copy(active_.begin() + 13, active_.begin() + 15, irq_registers_.begin()); + break; } - // MARK: - Condition tests. + // For all modes except FIQ: restore the final two registers to their appropriate values. + // For FIQ: save an additional five, then overwrite seven. + switch(target_mode) { + case Mode::FIQ: + // FIQ is incoming, so save registers 8 to 12 to the first five slots of the user registers. + std::copy(active_.begin() + 8, active_.begin() + 13, user_registers_.begin()); - /// @returns @c true if @c condition tests as true; @c false otherwise. - bool test(const Condition condition) const { - const auto ne = [&]() -> bool { - return zero_result_; - }; - const auto cs = [&]() -> bool { - return carry_flag_; - }; - const auto mi = [&]() -> bool { - return negative_flag_ & ConditionCode::Negative; - }; - const auto vs = [&]() -> bool { - return overflow_flag_ & ConditionCode::Negative; - }; - const auto hi = [&]() -> bool { - return carry_flag_ && zero_result_; - }; - const auto lt = [&]() -> bool { - return (negative_flag_ ^ overflow_flag_) & ConditionCode::Negative; - }; - const auto le = [&]() -> bool { - return !zero_result_ || lt(); - }; - - switch(condition) { - case Condition::EQ: return !ne(); - case Condition::NE: return ne(); - case Condition::CS: return cs(); - case Condition::CC: return !cs(); - case Condition::MI: return mi(); - case Condition::PL: return !mi(); - case Condition::VS: return vs(); - case Condition::VC: return !vs(); - - case Condition::HI: return hi(); - case Condition::LS: return !hi(); - case Condition::GE: return !lt(); - case Condition::LT: return lt(); - case Condition::GT: return !le(); - case Condition::LE: return le(); - - case Condition::AL: return true; - case Condition::NV: return false; - } - - return false; + // Replace R8 to R14. + std::copy(fiq_registers_.begin(), fiq_registers_.end(), active_.begin() + 8); + break; + case Mode::User: + std::copy(user_registers_.begin() + 5, user_registers_.end(), active_.begin() + 13); + break; + case Mode::Supervisor: + std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active_.begin() + 13); + break; + case Mode::IRQ: + std::copy(irq_registers_.begin(), irq_registers_.end(), active_.begin() + 13); + break; } - /// Sets current execution mode. - void set_mode(const Mode target_mode) { - if(mode_ == target_mode) { - return; - } - - // For outgoing modes other than FIQ, only save the final two registers for now; - // if the incoming mode is FIQ then the other five will be saved in the next switch. - // For FIQ, save all seven up front. - switch(mode_) { - // FIQ outgoing: save R8 to R14. - case Mode::FIQ: - std::copy(active_.begin() + 8, active_.begin() + 15, fiq_registers_.begin()); - break; - - // Non-FIQ outgoing: save R13 and R14. If saving to the user registers, - // use only the final two slots. - case Mode::User: - std::copy(active_.begin() + 13, active_.begin() + 15, user_registers_.begin() + 5); - break; - case Mode::Supervisor: - std::copy(active_.begin() + 13, active_.begin() + 15, supervisor_registers_.begin()); - break; - case Mode::IRQ: - std::copy(active_.begin() + 13, active_.begin() + 15, irq_registers_.begin()); - break; - } - - // For all modes except FIQ: restore the final two registers to their appropriate values. - // For FIQ: save an additional five, then overwrite seven. - switch(target_mode) { - case Mode::FIQ: - // FIQ is incoming, so save registers 8 to 12 to the first five slots of the user registers. - std::copy(active_.begin() + 8, active_.begin() + 13, user_registers_.begin()); - - // Replace R8 to R14. - std::copy(fiq_registers_.begin(), fiq_registers_.end(), active_.begin() + 8); - break; - case Mode::User: - std::copy(user_registers_.begin() + 5, user_registers_.end(), active_.begin() + 13); - break; - case Mode::Supervisor: - std::copy(supervisor_registers_.begin(), supervisor_registers_.end(), active_.begin() + 13); - break; - case Mode::IRQ: - std::copy(irq_registers_.begin(), irq_registers_.end(), active_.begin() + 13); - break; - } - - // If FIQ is outgoing then there's another five registers to restore. - if(mode_ == Mode::FIQ) { - std::copy(user_registers_.begin(), user_registers_.begin() + 5, active_.begin() + 8); - } - - mode_ = target_mode; + // If FIQ is outgoing then there's another five registers to restore. + if(mode_ == Mode::FIQ) { + std::copy(user_registers_.begin(), user_registers_.begin() + 5, active_.begin() + 8); } - uint32_t &operator[](const uint32_t offset) { - return active_[static_cast(offset)]; + mode_ = target_mode; + } + + uint32_t &operator[](const uint32_t offset) { + return active_[static_cast(offset)]; + } + + uint32_t operator[](const uint32_t offset) const { + return active_[static_cast(offset)]; + } + + /// @returns A reference to the register at @c offset. If @c force_user_mode is true, + /// this will the the user-mode register. Otherwise it'll be that for the current mode. These references + /// are guaranteed to remain valid only until the next mode change. + uint32_t ®(const bool force_user_mode, const uint32_t offset) { + switch(mode_) { + default: + case Mode::User: return active_[offset]; + + case Mode::Supervisor: + case Mode::IRQ: + if(force_user_mode && (offset == 13 || offset == 14)) { + return user_registers_[offset - 8]; + } + return active_[offset]; + + case Mode::FIQ: + if(force_user_mode && (offset >= 8 && offset < 15)) { + return user_registers_[offset - 8]; + } + return active_[offset]; } + } - uint32_t operator[](const uint32_t offset) const { - return active_[static_cast(offset)]; - } +private: + Mode mode_ = Mode::Supervisor; - /// @returns A reference to the register at @c offset. If @c force_user_mode is true, - /// this will the the user-mode register. Otherwise it'll be that for the current mode. These references - /// are guaranteed to remain valid only until the next mode change. - uint32_t ®(const bool force_user_mode, const uint32_t offset) { - switch(mode_) { - default: - case Mode::User: return active_[offset]; + uint32_t zero_result_ = 1; + uint32_t negative_flag_ = 0; + uint32_t interrupt_flags_ = ConditionCode::IRQDisable | ConditionCode::FIQDisable; + uint32_t carry_flag_ = 0; + uint32_t overflow_flag_ = 0; - case Mode::Supervisor: - case Mode::IRQ: - if(force_user_mode && (offset == 13 || offset == 14)) { - return user_registers_[offset - 8]; - } - return active_[offset]; + // Various shadow registers. + std::array user_registers_{}; + std::array fiq_registers_{}; + std::array irq_registers_{}; + std::array supervisor_registers_{}; - case Mode::FIQ: - if(force_user_mode && (offset >= 8 && offset < 15)) { - return user_registers_[offset - 8]; - } - return active_[offset]; - } - } - - private: - Mode mode_ = Mode::Supervisor; - - uint32_t zero_result_ = 1; - uint32_t negative_flag_ = 0; - uint32_t interrupt_flags_ = ConditionCode::IRQDisable | ConditionCode::FIQDisable; - uint32_t carry_flag_ = 0; - uint32_t overflow_flag_ = 0; - - // Various shadow registers. - std::array user_registers_{}; - std::array fiq_registers_{}; - std::array irq_registers_{}; - std::array supervisor_registers_{}; - - // The active register set. - std::array active_{}; + // The active register set. + std::array active_{}; }; } diff --git a/InstructionSets/M50740/Executor.hpp b/InstructionSets/M50740/Executor.hpp index e097cbbcf..bd9f8f7c4 100644 --- a/InstructionSets/M50740/Executor.hpp +++ b/InstructionSets/M50740/Executor.hpp @@ -76,37 +76,37 @@ private: Provides dynamic lookup of @c perform(Executor*). */ class PerformerLookup { - public: - PerformerLookup() { - fill(performers_); + public: + PerformerLookup() { + fill(performers_); + } + + Performer performer(const Operation operation, const AddressingMode addressing_mode) { + const auto index = + (int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) + + (int(addressing_mode) - MinAddressingMode); + return performers_[index]; + } + + private: + Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)]; + + template void fill_operation(Performer *target) { + *target = &Executor::perform; + + if constexpr (addressing_mode+1 <= MaxAddressingMode) { + fill_operation(target + 1); } + } - Performer performer(const Operation operation, const AddressingMode addressing_mode) { - const auto index = - (int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) + - (int(addressing_mode) - MinAddressingMode); - return performers_[index]; - } - - private: - Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)]; - - template void fill_operation(Performer *target) { - *target = &Executor::perform; - - if constexpr (addressing_mode+1 <= MaxAddressingMode) { - fill_operation(target + 1); - } - } - - template void fill(Performer *target) { - fill_operation(target); - target += 1 + MaxAddressingMode - MinAddressingMode; - - if constexpr (operation+1 <= MaxOperation) { - fill(target); - } + template void fill(Performer *target) { + fill_operation(target); + target += 1 + MaxAddressingMode - MinAddressingMode; + + if constexpr (operation+1 <= MaxOperation) { + fill(target); } + } }; inline static PerformerLookup performer_lookup_; diff --git a/InstructionSets/M68k/Executor.hpp b/InstructionSets/M68k/Executor.hpp index 53e8c0c21..b41aff812 100644 --- a/InstructionSets/M68k/Executor.hpp +++ b/InstructionSets/M68k/Executor.hpp @@ -85,79 +85,79 @@ public: private: class State: public NullFlowController { - public: - State(BusHandler &handler) : bus_handler_(handler) {} + public: + State(BusHandler &handler) : bus_handler_(handler) {} - void run(int &); - bool stopped = false; + void run(int &); + bool stopped = false; - void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value); - void write(DataSize size, uint32_t address, CPU::SlicedInt32 value); - template IntT read(uint32_t address, bool is_from_pc = false); - template void write(uint32_t address, IntT value); + void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value); + void write(DataSize size, uint32_t address, CPU::SlicedInt32 value); + template IntT read(uint32_t address, bool is_from_pc = false); + template void write(uint32_t address, IntT value); - template IntT read_pc(); + template IntT read_pc(); - // Processor state. - Status status; - CPU::SlicedInt32 program_counter; - CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7. - CPU::SlicedInt32 stack_pointers[2]; - uint32_t instruction_address; - uint16_t instruction_opcode; + // Processor state. + Status status; + CPU::SlicedInt32 program_counter; + CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7. + CPU::SlicedInt32 stack_pointers[2]; + uint32_t instruction_address; + uint16_t instruction_opcode; - // Things that are ephemerally duplicative of Status. - int active_stack_pointer = 0; - Status::FlagT should_trace = 0; + // Things that are ephemerally duplicative of Status. + int active_stack_pointer = 0; + Status::FlagT should_trace = 0; - // Bus state. - int interrupt_input = 0; + // Bus state. + int interrupt_input = 0; - // A lookup table to ensure that A7 is adjusted by 2 rather than 1 in - // postincrement and predecrement mode. - static constexpr uint32_t byte_increments[] = { - 1, 1, 1, 1, 1, 1, 1, 2 - }; + // A lookup table to ensure that A7 is adjusted by 2 rather than 1 in + // postincrement and predecrement mode. + static constexpr uint32_t byte_increments[] = { + 1, 1, 1, 1, 1, 1, 1, 2 + }; - // Flow control; Cf. Perform.hpp. - template void raise_exception(int); + // Flow control; Cf. Perform.hpp. + template void raise_exception(int); - void did_update_status(); + void did_update_status(); - template void complete_bcc(bool matched_condition, IntT offset); - void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset); - void bsr(uint32_t offset); - void jmp(uint32_t); - void jsr(uint32_t offset); - void rtr(); - void rts(); - void rte(); - void stop(); - void reset(); + template void complete_bcc(bool matched_condition, IntT offset); + void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset); + void bsr(uint32_t offset); + void jmp(uint32_t); + void jsr(uint32_t offset); + void rtr(); + void rts(); + void rte(); + void stop(); + void reset(); - void link(Preinstruction instruction, uint32_t offset); - void unlink(uint32_t &address); - void pea(uint32_t address); + void link(Preinstruction instruction, uint32_t offset); + void unlink(uint32_t &address); + void pea(uint32_t address); - void move_to_usp(uint32_t address); - void move_from_usp(uint32_t &address); + void move_to_usp(uint32_t address); + void move_from_usp(uint32_t &address); - template void movep(Preinstruction instruction, uint32_t source, uint32_t dest); - template void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest); - template void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest); + template void movep(Preinstruction instruction, uint32_t source, uint32_t dest); + template void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest); + template void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest); - void tas(Preinstruction instruction, uint32_t address); + void tas(Preinstruction instruction, uint32_t address); - private: - BusHandler &bus_handler_; - Predecoder decoder_; + private: + BusHandler &bus_handler_; + Predecoder decoder_; - struct EffectiveAddress { - CPU::SlicedInt32 value; - bool requires_fetch; - }; - EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index); - uint32_t index_8bitdisplacement(uint32_t); + struct EffectiveAddress { + CPU::SlicedInt32 value; + bool requires_fetch; + }; + EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index); + uint32_t index_8bitdisplacement(uint32_t); } state_; }; diff --git a/InstructionSets/x86/Decoder.hpp b/InstructionSets/x86/Decoder.hpp index 779ef3939..ab31fef40 100644 --- a/InstructionSets/x86/Decoder.hpp +++ b/InstructionSets/x86/Decoder.hpp @@ -233,11 +233,11 @@ private: // // So here's a thin non-templated shim to unblock initial PC Compatible development. class Decoder8086 { - public: - std::pair> decode(const uint8_t *source, std::size_t length); +public: + std::pair> decode(const uint8_t *source, std::size_t length); - private: - Decoder decoder; +private: + Decoder decoder; }; } diff --git a/InstructionSets/x86/Instruction.hpp b/InstructionSets/x86/Instruction.hpp index 2c2c4b1d4..2431da654 100644 --- a/InstructionSets/x86/Instruction.hpp +++ b/InstructionSets/x86/Instruction.hpp @@ -563,60 +563,60 @@ constexpr Operation rep_operation(Operation operation, Repetition repetition) { /// /// It cannot natively describe a base of ::None. class ScaleIndexBase { - public: - constexpr ScaleIndexBase() noexcept = default; - constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {} - constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept : - sib_(uint8_t( - scale << 6 | - (int(index != Source::None ? index : Source::eSP) << 3) | - int(base) - )) {} - constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {} - constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {} +public: + constexpr ScaleIndexBase() noexcept = default; + constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {} + constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept : + sib_(uint8_t( + scale << 6 | + (int(index != Source::None ? index : Source::eSP) << 3) | + int(base) + )) {} + constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {} + constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {} - /// @returns the power of two by which to multiply @c index() before adding it to @c base(). - constexpr int scale() const { - return sib_ >> 6; - } + /// @returns the power of two by which to multiply @c index() before adding it to @c base(). + constexpr int scale() const { + return sib_ >> 6; + } - /// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI. - constexpr Source index() const { - constexpr Source sources[] = { - Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI, - }; - static_assert(sizeof(sources) == 8); - return sources[(sib_ >> 3) & 0x7]; - } + /// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI. + constexpr Source index() const { + constexpr Source sources[] = { + Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI, + }; + static_assert(sizeof(sources) == 8); + return sources[(sib_ >> 3) & 0x7]; + } - /// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI. - constexpr Source base() const { - return Source(sib_ & 0x7); - } + /// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI. + constexpr Source base() const { + return Source(sib_ & 0x7); + } - constexpr uint8_t without_base() const { - return sib_ & ~0x3; - } + constexpr uint8_t without_base() const { + return sib_ & ~0x3; + } - bool operator ==(const ScaleIndexBase &rhs) const { - // Permit either exact equality or index and base being equal - // but transposed with a scale of 1. - return - (sib_ == rhs.sib_) || - ( - !scale() && !rhs.scale() && - rhs.index() == base() && - rhs.base() == index() - ); - } + bool operator ==(const ScaleIndexBase &rhs) const { + // Permit either exact equality or index and base being equal + // but transposed with a scale of 1. + return + (sib_ == rhs.sib_) || + ( + !scale() && !rhs.scale() && + rhs.index() == base() && + rhs.base() == index() + ); + } - operator uint8_t() const { - return sib_; - } + operator uint8_t() const { + return sib_; + } - private: - // Data is stored directly as an 80386 SIB byte. - uint8_t sib_ = 0; +private: + // Data is stored directly as an 80386 SIB byte. + uint8_t sib_ = 0; }; static_assert(sizeof(ScaleIndexBase) == 1); static_assert(alignof(ScaleIndexBase) == 1); @@ -632,69 +632,69 @@ static_assert(alignof(ScaleIndexBase) == 1); /// /// In all cases, the applicable segment is indicated by the instruction. class DataPointer { - public: - /// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect. - constexpr DataPointer(Source source) noexcept : source_(source) {} +public: + /// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect. + constexpr DataPointer(Source source) noexcept : source_(source) {} - /// Constricts a DataPointer with a source of ::Indirect and the specified sib. - constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {} + /// Constricts a DataPointer with a source of ::Indirect and the specified sib. + constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {} - /// Constructs a DataPointer with a source and SIB; use the source to indicate - /// whether the base field of the SIB is effective. - constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {} + /// Constructs a DataPointer with a source and SIB; use the source to indicate + /// whether the base field of the SIB is effective. + constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {} - /// Constructs an indirect DataPointer referencing the given base, index and scale. - /// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None. - constexpr DataPointer(Source base, Source index, int scale) noexcept : - source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase), - sib_(scale, index, base) {} + /// Constructs an indirect DataPointer referencing the given base, index and scale. + /// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None. + constexpr DataPointer(Source base, Source index, int scale) noexcept : + source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase), + sib_(scale, index, base) {} - constexpr bool operator ==(const DataPointer &rhs) const { - // Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase. - return - source_ == rhs.source_ && ( - source_ < Source::IndirectNoBase || - (source_ == Source::Indirect && sib_ == rhs.sib_) || - (source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base()) - ); + constexpr bool operator ==(const DataPointer &rhs) const { + // Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase. + return + source_ == rhs.source_ && ( + source_ < Source::IndirectNoBase || + (source_ == Source::Indirect && sib_ == rhs.sib_) || + (source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base()) + ); + } + + constexpr Source source() const { + return source_; + } + + constexpr int scale() const { + return sib_.scale(); + } + + constexpr Source index() const { + return sib_.index(); + } + + /// @returns The default segment to use for this access. + constexpr Source default_segment() const { + switch(source_) { + default: + case Source::IndirectNoBase: + return Source::None; + + case Source::Indirect: + switch(base()) { + default: return Source::DS; + case Source::eBP: + case Source::eSP: return Source::SS; + case Source::eDI: return Source::ES; + } } + } - constexpr Source source() const { - return source_; - } + constexpr Source base() const { + return sib_.base(); + } - constexpr int scale() const { - return sib_.scale(); - } - - constexpr Source index() const { - return sib_.index(); - } - - /// @returns The default segment to use for this access. - constexpr Source default_segment() const { - switch(source_) { - default: - case Source::IndirectNoBase: - return Source::None; - - case Source::Indirect: - switch(base()) { - default: return Source::DS; - case Source::eBP: - case Source::eSP: return Source::SS; - case Source::eDI: return Source::ES; - } - } - } - - constexpr Source base() const { - return sib_.base(); - } - - private: - Source source_ = Source::Indirect; - ScaleIndexBase sib_; +private: + Source source_ = Source::Indirect; + ScaleIndexBase sib_; }; template class Instruction { diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index e4c901d55..1828f67f2 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -51,692 +51,692 @@ class ConcreteMachine: public Activity::Source, public Configurable::Device { - private: - Log::Logger logger; +private: + Log::Logger logger; - // This fictitious clock rate just means '24 MIPS, please'; it's divided elsewhere. - static constexpr int ClockRate = 24'000'000; + // This fictitious clock rate just means '24 MIPS, please'; it's divided elsewhere. + static constexpr int ClockRate = 24'000'000; - // Runs for 24 cycles, distributing calls to the various ticking subsystems - // 'correctly' (i.e. correctly for the approximation in use). + // Runs for 24 cycles, distributing calls to the various ticking subsystems + // 'correctly' (i.e. correctly for the approximation in use). + // + // The implementation of this is coupled to the ClockRate above, hence its + // appearance here. + template + void macro_tick() { + macro_counter_ -= 24; + + // This is a 24-cycle window, so at 24Mhz macro_tick() is called at 1Mhz. + // Hence, required ticks are: // - // The implementation of this is coupled to the ClockRate above, hence its - // appearance here. - template - void macro_tick() { - macro_counter_ -= 24; + // * CPU: 24; + // * video: 24 / video_divider; + // * floppy: 8; + // * timers: 2; + // * sound: 1. - // This is a 24-cycle window, so at 24Mhz macro_tick() is called at 1Mhz. - // Hence, required ticks are: - // - // * CPU: 24; - // * video: 24 / video_divider; - // * floppy: 8; - // * timers: 2; - // * sound: 1. + tick_cpu_video<0, video_divider, original_speed>(); tick_cpu_video<1, video_divider, original_speed>(); + tick_cpu_video<2, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<3, video_divider, original_speed>(); tick_cpu_video<4, video_divider, original_speed>(); + tick_cpu_video<5, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<6, video_divider, original_speed>(); tick_cpu_video<7, video_divider, original_speed>(); + tick_cpu_video<8, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<9, video_divider, original_speed>(); tick_cpu_video<10, video_divider, original_speed>(); + tick_cpu_video<11, video_divider, original_speed>(); tick_floppy(); + tick_timers(); - tick_cpu_video<0, video_divider, original_speed>(); tick_cpu_video<1, video_divider, original_speed>(); - tick_cpu_video<2, video_divider, original_speed>(); tick_floppy(); - tick_cpu_video<3, video_divider, original_speed>(); tick_cpu_video<4, video_divider, original_speed>(); - tick_cpu_video<5, video_divider, original_speed>(); tick_floppy(); - tick_cpu_video<6, video_divider, original_speed>(); tick_cpu_video<7, video_divider, original_speed>(); - tick_cpu_video<8, video_divider, original_speed>(); tick_floppy(); - tick_cpu_video<9, video_divider, original_speed>(); tick_cpu_video<10, video_divider, original_speed>(); - tick_cpu_video<11, video_divider, original_speed>(); tick_floppy(); - tick_timers(); + tick_cpu_video<12, video_divider, original_speed>(); tick_cpu_video<13, video_divider, original_speed>(); + tick_cpu_video<14, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<15, video_divider, original_speed>(); tick_cpu_video<16, video_divider, original_speed>(); + tick_cpu_video<17, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<18, video_divider, original_speed>(); tick_cpu_video<19, video_divider, original_speed>(); + tick_cpu_video<20, video_divider, original_speed>(); tick_floppy(); + tick_cpu_video<21, video_divider, original_speed>(); tick_cpu_video<22, video_divider, original_speed>(); + tick_cpu_video<23, video_divider, original_speed>(); tick_floppy(); + tick_timers(); + tick_sound(); + } + int macro_counter_ = 0; - tick_cpu_video<12, video_divider, original_speed>(); tick_cpu_video<13, video_divider, original_speed>(); - tick_cpu_video<14, video_divider, original_speed>(); tick_floppy(); - tick_cpu_video<15, video_divider, original_speed>(); tick_cpu_video<16, video_divider, original_speed>(); - tick_cpu_video<17, video_divider, original_speed>(); tick_floppy(); - tick_cpu_video<18, video_divider, original_speed>(); tick_cpu_video<19, video_divider, original_speed>(); - tick_cpu_video<20, video_divider, original_speed>(); tick_floppy(); - tick_cpu_video<21, video_divider, original_speed>(); tick_cpu_video<22, video_divider, original_speed>(); - tick_cpu_video<23, video_divider, original_speed>(); tick_floppy(); - tick_timers(); - tick_sound(); - } - int macro_counter_ = 0; - - template - void tick_cpu_video() { - if constexpr (!(offset % video_divider)) { - tick_video(); - } - - // Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed. - if constexpr (original_speed && (offset & 7)) return; - if constexpr (offset & 1) return; - tick_cpu(); + template + void tick_cpu_video() { + if constexpr (!(offset % video_divider)) { + tick_video(); } - public: - ConcreteMachine( - const Analyser::Static::Acorn::ArchimedesTarget &target, - const ROMMachine::ROMFetcher &rom_fetcher - ) : executor_(*this, *this, *this) { - set_clock_rate(ClockRate); + // Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed. + if constexpr (original_speed && (offset & 7)) return; + if constexpr (offset & 1) return; + tick_cpu(); + } - constexpr ROM::Name risc_os = ROM::Name::AcornRISCOS311; - ROM::Request request(risc_os); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } +public: + ConcreteMachine( + const Analyser::Static::Acorn::ArchimedesTarget &target, + const ROMMachine::ROMFetcher &rom_fetcher + ) : executor_(*this, *this, *this) { + set_clock_rate(ClockRate); - executor_.bus.set_rom(roms.find(risc_os)->second); - insert_media(target.media); - - if(!target.media.disks.empty()) { - autoload_phase_ = AutoloadPhase::WaitingForStartup; - target_program_ = target.main_program; - } - - fill_pipeline(0); + constexpr ROM::Name risc_os = ROM::Name::AcornRISCOS311; + ROM::Request request(risc_os); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; } - void update_interrupts() { - using Exception = InstructionSet::ARM::Registers::Exception; + executor_.bus.set_rom(roms.find(risc_os)->second); + insert_media(target.media); - const int requests = executor_.bus.interrupt_mask(); - if((requests & InterruptRequests::FIQ) && executor_.registers().would_interrupt()) { - pipeline_.reschedule(Pipeline::SWISubversion::FIQ); - return; - } - if((requests & InterruptRequests::IRQ) && executor_.registers().would_interrupt()) { - pipeline_.reschedule(Pipeline::SWISubversion::IRQ); - } + if(!target.media.disks.empty()) { + autoload_phase_ = AutoloadPhase::WaitingForStartup; + target_program_ = target.main_program; } - void did_set_status() { - // This might have been a change of mode, so... - trans_ = executor_.registers().mode() == InstructionSet::ARM::Mode::User; - fill_pipeline(executor_.pc()); - update_interrupts(); + fill_pipeline(0); + } + + void update_interrupts() { + using Exception = InstructionSet::ARM::Registers::Exception; + + const int requests = executor_.bus.interrupt_mask(); + if((requests & InterruptRequests::FIQ) && executor_.registers().would_interrupt()) { + pipeline_.reschedule(Pipeline::SWISubversion::FIQ); + return; } - - void did_set_pc() { - fill_pipeline(executor_.pc()); + if((requests & InterruptRequests::IRQ) && executor_.registers().would_interrupt()) { + pipeline_.reschedule(Pipeline::SWISubversion::IRQ); } + } - bool should_swi(uint32_t comment) { - using Exception = InstructionSet::ARM::Registers::Exception; - using SWISubversion = Pipeline::SWISubversion; + void did_set_status() { + // This might have been a change of mode, so... + trans_ = executor_.registers().mode() == InstructionSet::ARM::Mode::User; + fill_pipeline(executor_.pc()); + update_interrupts(); + } - switch(pipeline_.swi_subversion()) { - case Pipeline::SWISubversion::None: { - // TODO: 400C1 to intercept create window 400C1 and positioning; then - // plot icon 400e2 to listen for icons in window. That'll give a click area. - // Probably also 400c2 which seems to be used to add icons to the icon bar. - // - // 400D4 for menus? + void did_set_pc() { + fill_pipeline(executor_.pc()); + } - const auto get_string = [&](uint32_t address, bool indirect) -> std::string { - std::string desc; - if(indirect) { - executor_.bus.read(address, address, false); - } - while(true) { - uint8_t next = 0; - executor_.bus.read(address, next, false); - if(next < 0x20) break; - desc.push_back(static_cast(next) & 0x7f); - ++address; - } - return desc; - }; + bool should_swi(uint32_t comment) { + using Exception = InstructionSet::ARM::Registers::Exception; + using SWISubversion = Pipeline::SWISubversion; - const uint32_t swi_code = comment & static_cast(~(1 << 17)); - switch(swi_code) { - // - // Passive monitoring traps, for automatic loading. - // + switch(pipeline_.swi_subversion()) { + case Pipeline::SWISubversion::None: { + // TODO: 400C1 to intercept create window 400C1 and positioning; then + // plot icon 400e2 to listen for icons in window. That'll give a click area. + // Probably also 400c2 which seems to be used to add icons to the icon bar. + // + // 400D4 for menus? - case 0x400e3: // Wimp_SetMode - case 0x65: // OS_ScreenMode - case 0x3f: // OS_CheckModeValid - if(autoload_phase_ == AutoloadPhase::OpeningProgram) { - autoload_phase_ = AutoloadPhase::Ended; - } - break; - - case 0x400d4: { - if(autoload_phase_ == AutoloadPhase::TestingMenu) { - autoload_phase_ = AutoloadPhase::Ended; - - uint32_t address = executor_.registers()[1] + 28; - bool should_left_click = true; - - while(true) { - uint32_t icon_flags; - uint32_t item_flags; - executor_.bus.read(address, item_flags, false); - executor_.bus.read(address + 8, icon_flags, false); - auto desc = get_string(address + 12, icon_flags & (1 << 8)); - - should_left_click &= - (desc == "Info") || - (desc == "Quit"); - - address += 24; - if(item_flags & (1 << 7)) break; - } - - if(should_left_click) { - // Exit the application menu, then click once further to launch. - CursorActionBuilder(cursor_actions_) - .move_to(IconBarProgramX - 128, IconBarY - 32) - .click(0) - .move_to(IconBarProgramX, IconBarY) - .click(0); - } - } - } break; - - // Wimp_OpenWindow. - case 0x400c5: { - const uint32_t address = executor_.registers()[1]; - uint32_t x1, y1, x2, y2; - executor_.bus.read(address + 4, x1, false); - executor_.bus.read(address + 8, y1, false); - executor_.bus.read(address + 12, x2, false); - executor_.bus.read(address + 16, y2, false); - - switch(autoload_phase_) { - default: break; - - case AutoloadPhase::WaitingForDiskContents: { - autoload_phase_ = AutoloadPhase::WaitingForTargetIcon; - - // Crib top left of window content. - target_window_[0] = static_cast(x1); - target_window_[1] = static_cast(y2); - } break; - - case AutoloadPhase::WaitingForStartup: - if(static_cast(y1) == -268435472) { // VERY TEMPORARY. TODO: find better trigger. - // Creation of any icon is used to spot that RISC OS has started up. - // - // Wait a further second, mouse down to (32, 240), left click. - // That'll trigger disk access. Then move up to the top left, - // in anticipation of the appearance of a window. - auto &builder = CursorActionBuilder(cursor_actions_) - .move_to(IconBarDriveX, IconBarY) - .click(0); - - if(target_program_.empty()) { - autoload_phase_ = AutoloadPhase::Ended; - } else { - autoload_phase_ = AutoloadPhase::WaitingForDiskContents; - builder.move_to(IconBarDriveX, 80); // Just a guess of 'close' to where the program to launch - // will probably be, to have the cursor broadly nearby. - } - } - break; - } - } break; - - // Wimp_CreateIcon, which also adds to the icon bar. - case 0x400c2: - switch(autoload_phase_) { - case AutoloadPhase::OpeningProgram: { - const uint32_t address = executor_.registers()[1]; - uint32_t handle; - executor_.bus.read(address, handle, false); - - // Test whether the program has added an icon on the right. - if(static_cast(handle) == -1) { - CursorActionBuilder(cursor_actions_) - .move_to(IconBarProgramX, IconBarY) - .click(1); - autoload_phase_ = AutoloadPhase::TestingMenu; - } - } break; - - default: break; - } - break; - - // Wimp_PlotIcon. - case 0x400e2: { - if(autoload_phase_ == AutoloadPhase::WaitingForTargetIcon) { - const uint32_t address = executor_.registers()[1]; - uint32_t flags; - executor_.bus.read(address + 16, flags, false); - - std::string desc; - if(flags & 1) { - desc = get_string(address + 20, flags & (1 << 8)); - } - - if(desc == target_program_) { - uint32_t x1, y1, x2, y2; - executor_.bus.read(address + 0, x1, false); - executor_.bus.read(address + 4, y1, false); - executor_.bus.read(address + 8, x2, false); - executor_.bus.read(address + 12, y2, false); - - autoload_phase_ = AutoloadPhase::OpeningProgram; - - // Some default icon sizing assumptions are baked in here. - const auto x_target = target_window_[0] + (static_cast(x1) + static_cast(x2)) / 2; - const auto y_target = target_window_[1] + static_cast(y1) + 24; - CursorActionBuilder(cursor_actions_) - .move_to(x_target >> 1, 256 - (y_target >> 2)) - .double_click(0); - } - } - } break; + const auto get_string = [&](uint32_t address, bool indirect) -> std::string { + std::string desc; + if(indirect) { + executor_.bus.read(address, address, false); } - } return true; + while(true) { + uint8_t next = 0; + executor_.bus.read(address, next, false); + if(next < 0x20) break; + desc.push_back(static_cast(next) & 0x7f); + ++address; + } + return desc; + }; - case SWISubversion::DataAbort: + const uint32_t swi_code = comment & static_cast(~(1 << 17)); + switch(swi_code) { + // + // Passive monitoring traps, for automatic loading. + // + + case 0x400e3: // Wimp_SetMode + case 0x65: // OS_ScreenMode + case 0x3f: // OS_CheckModeValid + if(autoload_phase_ == AutoloadPhase::OpeningProgram) { + autoload_phase_ = AutoloadPhase::Ended; + } + break; + + case 0x400d4: { + if(autoload_phase_ == AutoloadPhase::TestingMenu) { + autoload_phase_ = AutoloadPhase::Ended; + + uint32_t address = executor_.registers()[1] + 28; + bool should_left_click = true; + + while(true) { + uint32_t icon_flags; + uint32_t item_flags; + executor_.bus.read(address, item_flags, false); + executor_.bus.read(address + 8, icon_flags, false); + auto desc = get_string(address + 12, icon_flags & (1 << 8)); + + should_left_click &= + (desc == "Info") || + (desc == "Quit"); + + address += 24; + if(item_flags & (1 << 7)) break; + } + + if(should_left_click) { + // Exit the application menu, then click once further to launch. + CursorActionBuilder(cursor_actions_) + .move_to(IconBarProgramX - 128, IconBarY - 32) + .click(0) + .move_to(IconBarProgramX, IconBarY) + .click(0); + } + } + } break; + + // Wimp_OpenWindow. + case 0x400c5: { + const uint32_t address = executor_.registers()[1]; + uint32_t x1, y1, x2, y2; + executor_.bus.read(address + 4, x1, false); + executor_.bus.read(address + 8, y1, false); + executor_.bus.read(address + 12, x2, false); + executor_.bus.read(address + 16, y2, false); + + switch(autoload_phase_) { + default: break; + + case AutoloadPhase::WaitingForDiskContents: { + autoload_phase_ = AutoloadPhase::WaitingForTargetIcon; + + // Crib top left of window content. + target_window_[0] = static_cast(x1); + target_window_[1] = static_cast(y2); + } break; + + case AutoloadPhase::WaitingForStartup: + if(static_cast(y1) == -268435472) { // VERY TEMPORARY. TODO: find better trigger. + // Creation of any icon is used to spot that RISC OS has started up. + // + // Wait a further second, mouse down to (32, 240), left click. + // That'll trigger disk access. Then move up to the top left, + // in anticipation of the appearance of a window. + auto &builder = CursorActionBuilder(cursor_actions_) + .move_to(IconBarDriveX, IconBarY) + .click(0); + + if(target_program_.empty()) { + autoload_phase_ = AutoloadPhase::Ended; + } else { + autoload_phase_ = AutoloadPhase::WaitingForDiskContents; + builder.move_to(IconBarDriveX, 80); // Just a guess of 'close' to where the program to launch + // will probably be, to have the cursor broadly nearby. + } + } + break; + } + } break; + + // Wimp_CreateIcon, which also adds to the icon bar. + case 0x400c2: + switch(autoload_phase_) { + case AutoloadPhase::OpeningProgram: { + const uint32_t address = executor_.registers()[1]; + uint32_t handle; + executor_.bus.read(address, handle, false); + + // Test whether the program has added an icon on the right. + if(static_cast(handle) == -1) { + CursorActionBuilder(cursor_actions_) + .move_to(IconBarProgramX, IconBarY) + .click(1); + autoload_phase_ = AutoloadPhase::TestingMenu; + } + } break; + + default: break; + } + break; + + // Wimp_PlotIcon. + case 0x400e2: { + if(autoload_phase_ == AutoloadPhase::WaitingForTargetIcon) { + const uint32_t address = executor_.registers()[1]; + uint32_t flags; + executor_.bus.read(address + 16, flags, false); + + std::string desc; + if(flags & 1) { + desc = get_string(address + 20, flags & (1 << 8)); + } + + if(desc == target_program_) { + uint32_t x1, y1, x2, y2; + executor_.bus.read(address + 0, x1, false); + executor_.bus.read(address + 4, y1, false); + executor_.bus.read(address + 8, x2, false); + executor_.bus.read(address + 12, y2, false); + + autoload_phase_ = AutoloadPhase::OpeningProgram; + + // Some default icon sizing assumptions are baked in here. + const auto x_target = target_window_[0] + (static_cast(x1) + static_cast(x2)) / 2; + const auto y_target = target_window_[1] + static_cast(y1) + 24; + CursorActionBuilder(cursor_actions_) + .move_to(x_target >> 1, 256 - (y_target >> 2)) + .double_click(0); + } + } + } break; + } + } return true; + + case SWISubversion::DataAbort: // executor_.set_pc(executor_.pc() - 4); - executor_.registers().exception(); - break; + executor_.registers().exception(); + break; - // FIQ and IRQ decrement the PC because their apperance in the pipeline causes - // it to look as though they were fetched, but they weren't. - case SWISubversion::FIQ: - executor_.set_pc(executor_.pc() - 4); - executor_.registers().exception(); - break; - case SWISubversion::IRQ: - executor_.set_pc(executor_.pc() - 4); - executor_.registers().exception(); - break; - } - - did_set_pc(); - return false; + // FIQ and IRQ decrement the PC because their apperance in the pipeline causes + // it to look as though they were fetched, but they weren't. + case SWISubversion::FIQ: + executor_.set_pc(executor_.pc() - 4); + executor_.registers().exception(); + break; + case SWISubversion::IRQ: + executor_.set_pc(executor_.pc() - 4); + executor_.registers().exception(); + break; } - void update_clock_rates() { - video_divider_ = executor_.bus.video().clock_divider(); + did_set_pc(); + return false; + } + + void update_clock_rates() { + video_divider_ = executor_.bus.video().clock_divider(); + } + +private: + // MARK: - ScanProducer. + void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { + executor_.bus.video().crt().set_scan_target(scan_target); + } + Outputs::Display::ScanStatus get_scaled_scan_status() const override { + return executor_.bus.video().crt().get_scaled_scan_status() * video_divider_; + } + + // MARK: - TimedMachine. + bool use_original_speed() const { +#ifndef NDEBUG + // Debug mode: always run 'slowly' because that's less of a burden, and + // because it allows me to peer at problems with greater leisure. + return true; +#else + // As a first, blunt implementation: try to model something close + // to original speed if there have been 10 frame rate overages in total. + return executor_.bus.video().frame_rate_overages() > 10; +#endif + } + + + int video_divider_ = 1; + void run_for(Cycles cycles) override { + const auto run = [&](Cycles cycles) { + if(use_original_speed()) run_for(cycles); + else run_for(cycles); + }; + + // + // Short-circuit: no cursor actions means **just run**. + // + if(cursor_actions_.empty()) { + run(cycles); + return; + } + + // + // Mouse scripting; tick at a minimum of frame length. + // + static constexpr int TickFrequency = 24'000'000 / 50; + cursor_action_subcycle_ += cycles; + auto segments = cursor_action_subcycle_.divide(Cycles(TickFrequency)).as(); + while(segments--) { + Cycles next = Cycles(TickFrequency); + if(next > cycles) next = cycles; + cycles -= next; + + if(!cursor_actions_.empty()) { + const auto move_to_next = [&]() { + cursor_action_waited_ = 0; + cursor_actions_.erase(cursor_actions_.begin()); + }; + + const auto &action = cursor_actions_.front(); + switch(action.type) { + case CursorAction::Type::MoveTo: { + // A measure of where within the tip lies within + // the default RISC OS cursor. + constexpr int ActionPointOffset = 20; + constexpr int MaxStep = 24; + + const auto position = executor_.bus.video().cursor_location(); + if(!position) break; + const auto [x, y] = *position; + + auto x_diff = action.value.move_to.x - (x + ActionPointOffset); + auto y_diff = action.value.move_to.y - y; + + if(abs(x_diff) < 2 && abs(y_diff) < 2) { + move_to_next(); + break; + } + + if(abs(y_diff) > MaxStep || abs(x_diff) > MaxStep) { + if(abs(y_diff) > abs(x_diff)) { + x_diff = (x_diff * MaxStep + (abs(y_diff) >> 1)) / abs(y_diff); + y_diff = std::clamp(y_diff, -MaxStep, MaxStep); + } else { + y_diff = (y_diff * MaxStep + (abs(x_diff) >> 1)) / abs(x_diff); + x_diff = std::clamp(x_diff, -MaxStep, MaxStep); + } + } + get_mouse().move(x_diff, y_diff); + } break; + case CursorAction::Type::Wait: + cursor_action_waited_ += next.as(); + if(cursor_action_waited_ >= action.value.wait.duration) { + move_to_next(); + } + break; + case CursorAction::Type::Button: + get_mouse().set_button_pressed(action.value.button.button, action.value.button.down); + move_to_next(); + break; + case CursorAction::Type::SetPhase: + autoload_phase_ = action.value.set_phase.phase; + move_to_next(); + break; + } + } + + // + // Execution proper. + // + run(next); + } + } + + template + void run_for(Cycles cycles) { + macro_counter_ += cycles.as(); + + while(macro_counter_ > 0) { + switch(video_divider_) { + default: macro_tick<2, original_speed>(); break; + case 3: macro_tick<3, original_speed>(); break; + case 4: macro_tick<4, original_speed>(); break; + case 6: macro_tick<6, original_speed>(); break; + } + } + } + + void tick_cpu() { + const uint32_t instruction = advance_pipeline(executor_.pc() + 8); + InstructionSet::ARM::execute(instruction, executor_); + } + + void tick_timers() { executor_.bus.tick_timers(); } + void tick_sound() { executor_.bus.sound().tick(); } + void tick_video() { executor_.bus.video().tick(); } + + bool accelerate_loading_ = true; + void tick_floppy() { + executor_.bus.tick_floppy( + accelerate_loading_ ? (use_original_speed() ? 12 : 18) : 1 + ); + } + + // MARK: - MediaTarget + bool insert_media(const Analyser::Static::Media &media) override { + size_t c = 0; + for(auto &disk : media.disks) { + executor_.bus.set_disk(disk, c); + c++; + if(c == 4) break; + } + return true; + } + + // MARK: - Configuration options. + std::unique_ptr get_options() const final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->quickload = accelerate_loading_; + return options; + } + + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + accelerate_loading_ = options->quickload; + } + + // MARK: - AudioProducer + Outputs::Speaker::Speaker *get_speaker() override { + return executor_.bus.speaker(); + } + + // MARK: - Activity::Source. + void set_activity_observer(Activity::Observer *observer) final { + executor_.bus.set_activity_observer(observer); + } + + // MARK: - MappedKeyboardMachine. + MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override { + return &keyboard_mapper_; + } + Archimedes::KeyboardMapper keyboard_mapper_; + + void set_key_state(uint16_t key, bool is_pressed) override { + executor_.bus.keyboard().set_key_state(key, is_pressed); + } + + // MARK: - MouseMachine. + Inputs::Mouse &get_mouse() override { + return executor_.bus.keyboard().mouse(); + } + + // MARK: - ARM execution. + static constexpr auto arm_model = InstructionSet::ARM::Model::ARMv2; + using Executor = InstructionSet::ARM::Executor, ConcreteMachine>; + Executor executor_; + bool trans_ = false; + + void fill_pipeline(uint32_t pc) { + if(pipeline_.interrupt_next()) return; + advance_pipeline(pc); + advance_pipeline(pc + 4); + } + + uint32_t advance_pipeline(uint32_t pc) { + uint32_t instruction = 0; // Value should never be used; this avoids a spurious GCC warning. + const bool did_read = executor_.bus.read(pc, instruction, trans_); + return pipeline_.exchange( + did_read ? instruction : Pipeline::SWI, + did_read ? Pipeline::SWISubversion::None : Pipeline::SWISubversion::DataAbort); + } + + struct Pipeline { + enum SWISubversion: uint8_t { + None, + DataAbort, + IRQ, + FIQ, + }; + + static constexpr uint32_t SWI = 0xef'000000; + + uint32_t exchange(uint32_t next, SWISubversion subversion) { + const uint32_t result = upcoming_[active_].opcode; + latched_subversion_ = upcoming_[active_].subversion; + + upcoming_[active_].opcode = next; + upcoming_[active_].subversion = subversion; + active_ ^= 1; + + return result; + } + + SWISubversion swi_subversion() const { + return latched_subversion_; + } + + // TODO: one day, possibly: schedule the subversion one slot further into the future + // (i.e. active_ ^ 1) to allow one further instruction to occur as usual before the + // action paplies. That is, if interrupts take effect one instruction later after a flags + // change, which I don't yet know. + // + // In practice I got into a bit of a race condition between interrupt scheduling and + // flags changes, so have backed off for now. + void reschedule(SWISubversion subversion) { + upcoming_[active_].opcode = SWI; + upcoming_[active_].subversion = subversion; + } + + bool interrupt_next() const { + return upcoming_[active_].subversion == SWISubversion::IRQ || upcoming_[active_].subversion == SWISubversion::FIQ; } private: - // MARK: - ScanProducer. - void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { - executor_.bus.video().crt().set_scan_target(scan_target); - } - Outputs::Display::ScanStatus get_scaled_scan_status() const override { - return executor_.bus.video().crt().get_scaled_scan_status() * video_divider_; - } - - // MARK: - TimedMachine. - bool use_original_speed() const { -#ifndef NDEBUG - // Debug mode: always run 'slowly' because that's less of a burden, and - // because it allows me to peer at problems with greater leisure. - return true; -#else - // As a first, blunt implementation: try to model something close - // to original speed if there have been 10 frame rate overages in total. - return executor_.bus.video().frame_rate_overages() > 10; -#endif - } - - - int video_divider_ = 1; - void run_for(Cycles cycles) override { - const auto run = [&](Cycles cycles) { - if(use_original_speed()) run_for(cycles); - else run_for(cycles); - }; - - // - // Short-circuit: no cursor actions means **just run**. - // - if(cursor_actions_.empty()) { - run(cycles); - return; - } - - // - // Mouse scripting; tick at a minimum of frame length. - // - static constexpr int TickFrequency = 24'000'000 / 50; - cursor_action_subcycle_ += cycles; - auto segments = cursor_action_subcycle_.divide(Cycles(TickFrequency)).as(); - while(segments--) { - Cycles next = Cycles(TickFrequency); - if(next > cycles) next = cycles; - cycles -= next; - - if(!cursor_actions_.empty()) { - const auto move_to_next = [&]() { - cursor_action_waited_ = 0; - cursor_actions_.erase(cursor_actions_.begin()); - }; - - const auto &action = cursor_actions_.front(); - switch(action.type) { - case CursorAction::Type::MoveTo: { - // A measure of where within the tip lies within - // the default RISC OS cursor. - constexpr int ActionPointOffset = 20; - constexpr int MaxStep = 24; - - const auto position = executor_.bus.video().cursor_location(); - if(!position) break; - const auto [x, y] = *position; - - auto x_diff = action.value.move_to.x - (x + ActionPointOffset); - auto y_diff = action.value.move_to.y - y; - - if(abs(x_diff) < 2 && abs(y_diff) < 2) { - move_to_next(); - break; - } - - if(abs(y_diff) > MaxStep || abs(x_diff) > MaxStep) { - if(abs(y_diff) > abs(x_diff)) { - x_diff = (x_diff * MaxStep + (abs(y_diff) >> 1)) / abs(y_diff); - y_diff = std::clamp(y_diff, -MaxStep, MaxStep); - } else { - y_diff = (y_diff * MaxStep + (abs(x_diff) >> 1)) / abs(x_diff); - x_diff = std::clamp(x_diff, -MaxStep, MaxStep); - } - } - get_mouse().move(x_diff, y_diff); - } break; - case CursorAction::Type::Wait: - cursor_action_waited_ += next.as(); - if(cursor_action_waited_ >= action.value.wait.duration) { - move_to_next(); - } - break; - case CursorAction::Type::Button: - get_mouse().set_button_pressed(action.value.button.button, action.value.button.down); - move_to_next(); - break; - case CursorAction::Type::SetPhase: - autoload_phase_ = action.value.set_phase.phase; - move_to_next(); - break; - } - } - - // - // Execution proper. - // - run(next); - } - } - - template - void run_for(Cycles cycles) { - macro_counter_ += cycles.as(); - - while(macro_counter_ > 0) { - switch(video_divider_) { - default: macro_tick<2, original_speed>(); break; - case 3: macro_tick<3, original_speed>(); break; - case 4: macro_tick<4, original_speed>(); break; - case 6: macro_tick<6, original_speed>(); break; - } - } - } - - void tick_cpu() { - const uint32_t instruction = advance_pipeline(executor_.pc() + 8); - InstructionSet::ARM::execute(instruction, executor_); - } - - void tick_timers() { executor_.bus.tick_timers(); } - void tick_sound() { executor_.bus.sound().tick(); } - void tick_video() { executor_.bus.video().tick(); } - - bool accelerate_loading_ = true; - void tick_floppy() { - executor_.bus.tick_floppy( - accelerate_loading_ ? (use_original_speed() ? 12 : 18) : 1 - ); - } - - // MARK: - MediaTarget - bool insert_media(const Analyser::Static::Media &media) override { - size_t c = 0; - for(auto &disk : media.disks) { - executor_.bus.set_disk(disk, c); - c++; - if(c == 4) break; - } - return true; - } - - // MARK: - Configuration options. - std::unique_ptr get_options() const final { - auto options = std::make_unique(Configurable::OptionsType::UserFriendly); - options->quickload = accelerate_loading_; - return options; - } - - void set_options(const std::unique_ptr &str) final { - const auto options = dynamic_cast(str.get()); - accelerate_loading_ = options->quickload; - } - - // MARK: - AudioProducer - Outputs::Speaker::Speaker *get_speaker() override { - return executor_.bus.speaker(); - } - - // MARK: - Activity::Source. - void set_activity_observer(Activity::Observer *observer) final { - executor_.bus.set_activity_observer(observer); - } - - // MARK: - MappedKeyboardMachine. - MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override { - return &keyboard_mapper_; - } - Archimedes::KeyboardMapper keyboard_mapper_; - - void set_key_state(uint16_t key, bool is_pressed) override { - executor_.bus.keyboard().set_key_state(key, is_pressed); - } - - // MARK: - MouseMachine. - Inputs::Mouse &get_mouse() override { - return executor_.bus.keyboard().mouse(); - } - - // MARK: - ARM execution. - static constexpr auto arm_model = InstructionSet::ARM::Model::ARMv2; - using Executor = InstructionSet::ARM::Executor, ConcreteMachine>; - Executor executor_; - bool trans_ = false; - - void fill_pipeline(uint32_t pc) { - if(pipeline_.interrupt_next()) return; - advance_pipeline(pc); - advance_pipeline(pc + 4); - } - - uint32_t advance_pipeline(uint32_t pc) { - uint32_t instruction = 0; // Value should never be used; this avoids a spurious GCC warning. - const bool did_read = executor_.bus.read(pc, instruction, trans_); - return pipeline_.exchange( - did_read ? instruction : Pipeline::SWI, - did_read ? Pipeline::SWISubversion::None : Pipeline::SWISubversion::DataAbort); - } - - struct Pipeline { - enum SWISubversion: uint8_t { - None, - DataAbort, - IRQ, - FIQ, - }; - - static constexpr uint32_t SWI = 0xef'000000; - - uint32_t exchange(uint32_t next, SWISubversion subversion) { - const uint32_t result = upcoming_[active_].opcode; - latched_subversion_ = upcoming_[active_].subversion; - - upcoming_[active_].opcode = next; - upcoming_[active_].subversion = subversion; - active_ ^= 1; - - return result; - } - - SWISubversion swi_subversion() const { - return latched_subversion_; - } - - // TODO: one day, possibly: schedule the subversion one slot further into the future - // (i.e. active_ ^ 1) to allow one further instruction to occur as usual before the - // action paplies. That is, if interrupts take effect one instruction later after a flags - // change, which I don't yet know. - // - // In practice I got into a bit of a race condition between interrupt scheduling and - // flags changes, so have backed off for now. - void reschedule(SWISubversion subversion) { - upcoming_[active_].opcode = SWI; - upcoming_[active_].subversion = subversion; - } - - bool interrupt_next() const { - return upcoming_[active_].subversion == SWISubversion::IRQ || upcoming_[active_].subversion == SWISubversion::FIQ; - } - - private: - struct Stage { - uint32_t opcode; - SWISubversion subversion = SWISubversion::None; - }; - Stage upcoming_[2]; - int active_ = 0; - - SWISubversion latched_subversion_; - } pipeline_; - - // MARK: - Autoload, including cursor scripting. - - enum class AutoloadPhase { - WaitingForStartup, - WaitingForDiskContents, - WaitingForTargetIcon, - OpeningProgram, - TestingMenu, - Ended, + struct Stage { + uint32_t opcode; + SWISubversion subversion = SWISubversion::None; }; - AutoloadPhase autoload_phase_ = AutoloadPhase::Ended; - std::string target_program_; + Stage upcoming_[2]; + int active_ = 0; - struct CursorAction { - enum class Type { - MoveTo, - Button, - Wait, - SetPhase, - } type; + SWISubversion latched_subversion_; + } pipeline_; - union { - struct { - int x, y; - } move_to; - struct { - int duration; - } wait; - struct { - int button; - bool down; - } button; - struct { - AutoloadPhase phase; - } set_phase; - } value; + // MARK: - Autoload, including cursor scripting. - static CursorAction move_to(int x, int y) { - CursorAction action; - action.type = Type::MoveTo; - action.value.move_to.x = x; - action.value.move_to.y = y; - return action; - } - static CursorAction wait(int duration) { - CursorAction action; - action.type = Type::Wait; - action.value.wait.duration = duration; - return action; - } - static CursorAction button(int button, bool down) { - CursorAction action; - action.type = Type::Button; - action.value.button.button = button; - action.value.button.down = down; - return action; - } - static CursorAction set_phase(AutoloadPhase phase) { - CursorAction action; - action.type = Type::SetPhase; - action.value.set_phase.phase = phase; - return action; - } - }; + enum class AutoloadPhase { + WaitingForStartup, + WaitingForDiskContents, + WaitingForTargetIcon, + OpeningProgram, + TestingMenu, + Ended, + }; + AutoloadPhase autoload_phase_ = AutoloadPhase::Ended; + std::string target_program_; - std::vector cursor_actions_; + struct CursorAction { + enum class Type { + MoveTo, + Button, + Wait, + SetPhase, + } type; - struct CursorActionBuilder { - CursorActionBuilder(std::vector &actions) : actions_(actions) {} + union { + struct { + int x, y; + } move_to; + struct { + int duration; + } wait; + struct { + int button; + bool down; + } button; + struct { + AutoloadPhase phase; + } set_phase; + } value; - CursorActionBuilder &wait(int duration) { - actions_.push_back(CursorAction::wait(duration)); + static CursorAction move_to(int x, int y) { + CursorAction action; + action.type = Type::MoveTo; + action.value.move_to.x = x; + action.value.move_to.y = y; + return action; + } + static CursorAction wait(int duration) { + CursorAction action; + action.type = Type::Wait; + action.value.wait.duration = duration; + return action; + } + static CursorAction button(int button, bool down) { + CursorAction action; + action.type = Type::Button; + action.value.button.button = button; + action.value.button.down = down; + return action; + } + static CursorAction set_phase(AutoloadPhase phase) { + CursorAction action; + action.type = Type::SetPhase; + action.value.set_phase.phase = phase; + return action; + } + }; + + std::vector cursor_actions_; + + struct CursorActionBuilder { + CursorActionBuilder(std::vector &actions) : actions_(actions) {} + + CursorActionBuilder &wait(int duration) { + actions_.push_back(CursorAction::wait(duration)); + return *this; + } + + CursorActionBuilder &move_to(int x, int y) { + // Special case: if this sets a move_to when one is in progress, + // just update the target. + if(!actions_.empty() && actions_.back().type == CursorAction::Type::MoveTo) { + actions_.back().value.move_to.x = x; + actions_.back().value.move_to.y = y; return *this; } - CursorActionBuilder &move_to(int x, int y) { - // Special case: if this sets a move_to when one is in progress, - // just update the target. - if(!actions_.empty() && actions_.back().type == CursorAction::Type::MoveTo) { - actions_.back().value.move_to.x = x; - actions_.back().value.move_to.y = y; - return *this; - } + actions_.push_back(CursorAction::move_to(x, y)); + return *this; + } - actions_.push_back(CursorAction::move_to(x, y)); - return *this; - } + CursorActionBuilder &click(int button) { + actions_.push_back(CursorAction::button(button, true)); + actions_.push_back(CursorAction::wait(6'000'000)); + actions_.push_back(CursorAction::button(button, false)); + return *this; + } - CursorActionBuilder &click(int button) { - actions_.push_back(CursorAction::button(button, true)); - actions_.push_back(CursorAction::wait(6'000'000)); - actions_.push_back(CursorAction::button(button, false)); - return *this; - } + CursorActionBuilder &double_click(int button) { + actions_.push_back(CursorAction::button(button, true)); + actions_.push_back(CursorAction::wait(6'000'000)); + actions_.push_back(CursorAction::button(button, false)); + actions_.push_back(CursorAction::wait(6'000'000)); + actions_.push_back(CursorAction::button(button, true)); + actions_.push_back(CursorAction::wait(6'000'000)); + actions_.push_back(CursorAction::button(button, false)); + return *this; + } - CursorActionBuilder &double_click(int button) { - actions_.push_back(CursorAction::button(button, true)); - actions_.push_back(CursorAction::wait(6'000'000)); - actions_.push_back(CursorAction::button(button, false)); - actions_.push_back(CursorAction::wait(6'000'000)); - actions_.push_back(CursorAction::button(button, true)); - actions_.push_back(CursorAction::wait(6'000'000)); - actions_.push_back(CursorAction::button(button, false)); - return *this; - } + CursorActionBuilder &set_phase(AutoloadPhase phase) { + actions_.push_back(CursorAction::set_phase(phase)); + return *this; + } - CursorActionBuilder &set_phase(AutoloadPhase phase) { - actions_.push_back(CursorAction::set_phase(phase)); - return *this; - } + std::vector &actions_; + }; + static constexpr int IconBarY = 240; + static constexpr int IconBarProgramX = 532; + static constexpr int IconBarDriveX = 32; - std::vector &actions_; - }; - static constexpr int IconBarY = 240; - static constexpr int IconBarProgramX = 532; - static constexpr int IconBarDriveX = 32; + std::vector &begin(); - std::vector &begin(); - - Cycles cursor_action_subcycle_; - int cursor_action_waited_; - int32_t target_window_[2]; + Cycles cursor_action_subcycle_; + int cursor_action_waited_; + int32_t target_window_[2]; }; } diff --git a/Machines/Acorn/Archimedes/MemoryController.hpp b/Machines/Acorn/Archimedes/MemoryController.hpp index e100a87db..06d6a796e 100644 --- a/Machines/Acorn/Archimedes/MemoryController.hpp +++ b/Machines/Acorn/Archimedes/MemoryController.hpp @@ -236,275 +236,275 @@ struct MemoryController { ioc_.set_activity_observer(observer); } - private: - Log::Logger logger; +private: + Log::Logger logger; - enum class ReadZone { - LogicallyMappedRAM, - PhysicallyMappedRAM, - IOControllers, - LowROM, - HighROM, - }; - enum class WriteZone { - LogicallyMappedRAM, - PhysicallyMappedRAM, - IOControllers, - VideoController, - DMAAndMEMC, - AddressTranslator, - }; - template - using Zone = std::conditional_t; + enum class ReadZone { + LogicallyMappedRAM, + PhysicallyMappedRAM, + IOControllers, + LowROM, + HighROM, + }; + enum class WriteZone { + LogicallyMappedRAM, + PhysicallyMappedRAM, + IOControllers, + VideoController, + DMAAndMEMC, + AddressTranslator, + }; + template + using Zone = std::conditional_t; - template - static std::array, 0x20> zones() { - std::array, 0x20> zones{}; - for(size_t c = 0; c < zones.size(); c++) { - const auto address = c << 21; - if(address < 0x200'0000) { - zones[c] = Zone::LogicallyMappedRAM; - } else if(address < 0x300'0000) { - zones[c] = Zone::PhysicallyMappedRAM; - } else if(address < 0x340'0000) { - zones[c] = Zone::IOControllers; - } else if(address < 0x360'0000) { - if constexpr (is_read) { - zones[c] = Zone::LowROM; - } else { - zones[c] = Zone::VideoController; - } - } else if(address < 0x380'0000) { - if constexpr (is_read) { - zones[c] = Zone::LowROM; - } else { - zones[c] = Zone::DMAAndMEMC; - } + template + static std::array, 0x20> zones() { + std::array, 0x20> zones{}; + for(size_t c = 0; c < zones.size(); c++) { + const auto address = c << 21; + if(address < 0x200'0000) { + zones[c] = Zone::LogicallyMappedRAM; + } else if(address < 0x300'0000) { + zones[c] = Zone::PhysicallyMappedRAM; + } else if(address < 0x340'0000) { + zones[c] = Zone::IOControllers; + } else if(address < 0x360'0000) { + if constexpr (is_read) { + zones[c] = Zone::LowROM; } else { - if constexpr (is_read) { - zones[c] = Zone::HighROM; - } else { - zones[c] = Zone::AddressTranslator; - } + zones[c] = Zone::VideoController; + } + } else if(address < 0x380'0000) { + if constexpr (is_read) { + zones[c] = Zone::LowROM; + } else { + zones[c] = Zone::DMAAndMEMC; + } + } else { + if constexpr (is_read) { + zones[c] = Zone::HighROM; + } else { + zones[c] = Zone::AddressTranslator; } } - return zones; + } + return zones; + } + + bool has_moved_rom_ = false; + std::array rom_; + std::array ram_{}; + InputOutputController ioc_; + + template + IntT &physical_ram(uint32_t address) { + address = aligned(address); + address &= (ram_.size() - 1); + return *reinterpret_cast(&ram_[address]); + } + + template + IntT &high_rom(uint32_t address) { + address = aligned(address); + return *reinterpret_cast(&rom_[address & (rom_.size() - 1)]); + } + + std::array read_zones_ = zones(); + const std::array write_zones_ = zones(); + + // Control register values. + bool os_mode_ = false; + bool sound_dma_enable_ = false; + bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily. + + enum class DynamicRAMRefresh { + None = 0b00, + DuringFlyback = 0b01, + Continuous = 0b11, + } dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value. + + enum class ROMAccessTime { + ns450 = 0b00, + ns325 = 0b01, + ns200 = 0b10, + ns200with60nsNibble = 0b11, + } high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450; + + enum class PageSize { + kb4 = 0b00, + kb8 = 0b01, + kb16 = 0b10, + kb32 = 0b11, + } page_size_ = PageSize::kb4; + int page_address_shift_ = 12; + uint32_t page_adddress_mask_ = 0xffff; + + // Address translator. + // + // MEMC contains one entry per a physical page number, indicating where it goes logically. + // Any logical access is tested against all 128 mappings. So that's backwards compared to + // the ideal for an emulator, which would map from logical to physical, even if a lot more + // compact — there are always 128 physical pages; there are up to 8192 logical pages. + // + // So captured here are both the physical -> logical map as representative of the real + // hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt) + // from the other. + + // Physical to logical mapping. + std::array pages_{}; + + // Logical to physical mapping; this is divided by 'access mode' + // (i.e. the combination of read/write, trans and OS mode flags, + // as multipliexed by the @c mapping() function) because mapping + // varies by mode — not just in terms of restricting access, but + // actually presenting different memory. + using MapTarget = std::array; + std::array mapping_; + + template + MapTarget &mapping(bool trans, bool os_mode) { + const size_t index = (is_read ? 1 : 0) | (os_mode ? 2 : 0) | ((trans && !os_mode) ? 4 : 0); + return mapping_[index]; + } + + bool map_dirty_ = true; + + /// @returns A pointer to somewhere in @c ram_ if RAM is mapped to this area, or a pointer to somewhere lower than @c ram_.data() otherwise. + template + IntT *logical_ram(uint32_t address, bool trans) { + // Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive + // mapping process. It can be eliminated when the process is improved. + if(map_dirty_) { + update_mapping(); + map_dirty_ = false; + } + address = aligned(address); + address &= 0x1ff'ffff; + const size_t page = address >> page_address_shift_; + + const auto &map = mapping(trans, os_mode_); + address &= page_adddress_mask_; + return reinterpret_cast(&map[page][address]); + } + + void update_mapping() { + // For each physical page, project it into logical space. + switch(page_size_) { + default: + case PageSize::kb4: update_mapping(); break; + case PageSize::kb8: update_mapping(); break; + case PageSize::kb16: update_mapping(); break; + case PageSize::kb32: update_mapping(); break; + } + } + + template + void update_mapping() { + // Clear all logical mappings. + for(auto &map: mapping_) { + // Seed all pointers to an address sufficiently far lower than the beginning of RAM as to mark + // the entire page as unmapped no matter what offset is added. + std::fill(map.begin(), map.end(), ram_.data() - 32768); } - bool has_moved_rom_ = false; - std::array rom_; - std::array ram_{}; - InputOutputController ioc_; + // For each physical page, project it into logical space + // and store it. + for(const auto page: pages_) { + uint32_t physical, logical; - template - IntT &physical_ram(uint32_t address) { - address = aligned(address); - address &= (ram_.size() - 1); - return *reinterpret_cast(&ram_[address]); - } + switch(size) { + case PageSize::kb4: + // 4kb: + // A[6:0] -> PPN[6:0] + // A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages + physical = page & BitMask<6, 0>::value; - template - IntT &high_rom(uint32_t address) { - address = aligned(address); - return *reinterpret_cast(&rom_[address & (rom_.size() - 1)]); - } + physical <<= 12; - std::array read_zones_ = zones(); - const std::array write_zones_ = zones(); + logical = (page & BitMask<11, 10>::value) << 1; + logical |= (page & BitMask<22, 12>::value) >> 12; + break; - // Control register values. - bool os_mode_ = false; - bool sound_dma_enable_ = false; - bool video_dma_enable_ = false; // "Unaffected" by reset, so here picked arbitrarily. + case PageSize::kb8: + // 8kb: + // A[0] -> PPN[6]; A[6:1] -> PPN[5:0] + // A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages + physical = (page & BitMask<0, 0>::value) << 6; + physical |= (page & BitMask<6, 1>::value) >> 1; - enum class DynamicRAMRefresh { - None = 0b00, - DuringFlyback = 0b01, - Continuous = 0b11, - } dynamic_ram_refresh_ = DynamicRAMRefresh::None; // State at reset is undefined; constrain to a valid enum value. + physical <<= 13; - enum class ROMAccessTime { - ns450 = 0b00, - ns325 = 0b01, - ns200 = 0b10, - ns200with60nsNibble = 0b11, - } high_rom_access_time_ = ROMAccessTime::ns450, low_rom_access_time_ = ROMAccessTime::ns450; + logical = page & BitMask<11, 10>::value; + logical |= (page & BitMask<22, 13>::value) >> 13; + break; - enum class PageSize { - kb4 = 0b00, - kb8 = 0b01, - kb16 = 0b10, - kb32 = 0b11, - } page_size_ = PageSize::kb4; - int page_address_shift_ = 12; - uint32_t page_adddress_mask_ = 0xffff; + case PageSize::kb16: + // 16kb: + // A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0] + // A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages + physical = (page & BitMask<1, 0>::value) << 5; + physical |= (page & BitMask<6, 2>::value) >> 2; - // Address translator. - // - // MEMC contains one entry per a physical page number, indicating where it goes logically. - // Any logical access is tested against all 128 mappings. So that's backwards compared to - // the ideal for an emulator, which would map from logical to physical, even if a lot more - // compact — there are always 128 physical pages; there are up to 8192 logical pages. - // - // So captured here are both the physical -> logical map as representative of the real - // hardware, and the reverse logical -> physical map, which is built (and rebuilt, and rebuilt) - // from the other. + physical <<= 14; - // Physical to logical mapping. - std::array pages_{}; + logical = (page & BitMask<11, 10>::value) >> 1; + logical |= (page & BitMask<22, 14>::value) >> 14; + break; - // Logical to physical mapping; this is divided by 'access mode' - // (i.e. the combination of read/write, trans and OS mode flags, - // as multipliexed by the @c mapping() function) because mapping - // varies by mode — not just in terms of restricting access, but - // actually presenting different memory. - using MapTarget = std::array; - std::array mapping_; + case PageSize::kb32: + // 32kb: + // A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0] + // A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages + physical = (page & BitMask<1, 1>::value) << 5; + physical |= (page & BitMask<2, 2>::value) << 3; + physical |= (page & BitMask<0, 0>::value) << 4; + physical |= (page & BitMask<6, 3>::value) >> 3; - template - MapTarget &mapping(bool trans, bool os_mode) { - const size_t index = (is_read ? 1 : 0) | (os_mode ? 2 : 0) | ((trans && !os_mode) ? 4 : 0); - return mapping_[index]; - } + physical <<= 15; - bool map_dirty_ = true; - - /// @returns A pointer to somewhere in @c ram_ if RAM is mapped to this area, or a pointer to somewhere lower than @c ram_.data() otherwise. - template - IntT *logical_ram(uint32_t address, bool trans) { - // Possibly TODO: this recompute-if-dirty flag is supposed to ameliorate for an expensive - // mapping process. It can be eliminated when the process is improved. - if(map_dirty_) { - update_mapping(); - map_dirty_ = false; + logical = (page & BitMask<11, 10>::value) >> 2; + logical |= (page & BitMask<22, 15>::value) >> 15; + break; } - address = aligned(address); - address &= 0x1ff'ffff; - const size_t page = address >> page_address_shift_; - - const auto &map = mapping(trans, os_mode_); - address &= page_adddress_mask_; - return reinterpret_cast(&map[page][address]); - } - - void update_mapping() { - // For each physical page, project it into logical space. - switch(page_size_) { - default: - case PageSize::kb4: update_mapping(); break; - case PageSize::kb8: update_mapping(); break; - case PageSize::kb16: update_mapping(); break; - case PageSize::kb32: update_mapping(); break; - } - } - - template - void update_mapping() { - // Clear all logical mappings. - for(auto &map: mapping_) { - // Seed all pointers to an address sufficiently far lower than the beginning of RAM as to mark - // the entire page as unmapped no matter what offset is added. - std::fill(map.begin(), map.end(), ram_.data() - 32768); - } - - // For each physical page, project it into logical space - // and store it. - for(const auto page: pages_) { - uint32_t physical, logical; - - switch(size) { - case PageSize::kb4: - // 4kb: - // A[6:0] -> PPN[6:0] - // A[11:10] -> LPN[12:11]; A[22:12] -> LPN[10:0] i.e. 8192 logical pages - physical = page & BitMask<6, 0>::value; - - physical <<= 12; - - logical = (page & BitMask<11, 10>::value) << 1; - logical |= (page & BitMask<22, 12>::value) >> 12; - break; - - case PageSize::kb8: - // 8kb: - // A[0] -> PPN[6]; A[6:1] -> PPN[5:0] - // A[11:10] -> LPN[11:10]; A[22:13] -> LPN[9:0] i.e. 4096 logical pages - physical = (page & BitMask<0, 0>::value) << 6; - physical |= (page & BitMask<6, 1>::value) >> 1; - - physical <<= 13; - - logical = page & BitMask<11, 10>::value; - logical |= (page & BitMask<22, 13>::value) >> 13; - break; - - case PageSize::kb16: - // 16kb: - // A[1:0] -> PPN[6:5]; A[6:2] -> PPN[4:0] - // A[11:10] -> LPN[10:9]; A[22:14] -> LPN[8:0] i.e. 2048 logical pages - physical = (page & BitMask<1, 0>::value) << 5; - physical |= (page & BitMask<6, 2>::value) >> 2; - - physical <<= 14; - - logical = (page & BitMask<11, 10>::value) >> 1; - logical |= (page & BitMask<22, 14>::value) >> 14; - break; - - case PageSize::kb32: - // 32kb: - // A[1] -> PPN[6]; A[2] -> PPN[5]; A[0] -> PPN[4]; A[6:3] -> PPN[3:0] - // A[11:10] -> LPN[9:8]; A[22:15] -> LPN[7:0] i.e. 1024 logical pages - physical = (page & BitMask<1, 1>::value) << 5; - physical |= (page & BitMask<2, 2>::value) << 3; - physical |= (page & BitMask<0, 0>::value) << 4; - physical |= (page & BitMask<6, 3>::value) >> 3; - - physical <<= 15; - - logical = (page & BitMask<11, 10>::value) >> 2; - logical |= (page & BitMask<22, 15>::value) >> 15; - break; - } // printf("%08x => physical %d -> logical %d\n", page, (physical >> 15), logical); - // TODO: consider clashes. - // TODO: what if there's less than 4mb present? - const auto target = &ram_[physical]; + // TODO: consider clashes. + // TODO: what if there's less than 4mb present? + const auto target = &ram_[physical]; - const auto set_supervisor = [&](bool read, bool write) { - if(read) mapping(false, false)[logical] = target; - if(write) mapping(false, false)[logical] = target; - }; + const auto set_supervisor = [&](bool read, bool write) { + if(read) mapping(false, false)[logical] = target; + if(write) mapping(false, false)[logical] = target; + }; - const auto set_os = [&](bool read, bool write) { - if(read) mapping(true, true)[logical] = target; - if(write) mapping(true, true)[logical] = target; - }; + const auto set_os = [&](bool read, bool write) { + if(read) mapping(true, true)[logical] = target; + if(write) mapping(true, true)[logical] = target; + }; - const auto set_user = [&](bool read, bool write) { - if(read) mapping(true, false)[logical] = target; - if(write) mapping(true, false)[logical] = target; - }; + const auto set_user = [&](bool read, bool write) { + if(read) mapping(true, false)[logical] = target; + if(write) mapping(true, false)[logical] = target; + }; - set_supervisor(true, true); - switch((page >> 8) & 3) { - case 0b00: - set_os(true, true); - set_user(true, true); - break; - case 0b01: - set_os(true, true); - set_user(true, false); - break; - default: - set_os(true, false); - set_user(false, false); - break; - } + set_supervisor(true, true); + switch((page >> 8) & 3) { + case 0b00: + set_os(true, true); + set_user(true, true); + break; + case 0b01: + set_os(true, true); + set_user(true, false); + break; + default: + set_os(true, false); + set_user(false, false); + break; } } + } }; } diff --git a/Machines/Acorn/Electron/Electron.cpp b/Machines/Acorn/Electron/Electron.cpp index eb1b7de4e..8bdba0b7b 100644 --- a/Machines/Acorn/Electron/Electron.cpp +++ b/Machines/Acorn/Electron/Electron.cpp @@ -50,735 +50,735 @@ template class ConcreteMachine: public Activity::Source, public SCSI::Bus::Observer, public ClockingHint::Observer { - public: - ConcreteMachine(const Analyser::Static::Acorn::ElectronTarget &target, const ROMMachine::ROMFetcher &rom_fetcher) : - m6502_(*this), - scsi_bus_(4'000'000), - hard_drive_(scsi_bus_, 0), - scsi_device_(scsi_bus_.add_device()), - video_(ram_), - sound_generator_(audio_queue_), - speaker_(sound_generator_) { - memset(key_states_, 0, sizeof(key_states_)); - for(int c = 0; c < 16; c++) - memset(roms_[c], 0xff, 16384); +public: + ConcreteMachine(const Analyser::Static::Acorn::ElectronTarget &target, const ROMMachine::ROMFetcher &rom_fetcher) : + m6502_(*this), + scsi_bus_(4'000'000), + hard_drive_(scsi_bus_, 0), + scsi_device_(scsi_bus_.add_device()), + video_(ram_), + sound_generator_(audio_queue_), + speaker_(sound_generator_) { + memset(key_states_, 0, sizeof(key_states_)); + for(int c = 0; c < 16; c++) + memset(roms_[c], 0xff, 16384); - tape_.set_delegate(this); - set_clock_rate(2000000); + tape_.set_delegate(this); + set_clock_rate(2000000); - speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); - speaker_.set_high_frequency_cutoff(6000); + speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); + speaker_.set_high_frequency_cutoff(6000); - ::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100); + ::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100); + if(target.has_pres_adfs) { + request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2); + } + if(target.has_acorn_adfs) { + request = request && ::ROM::Request(::ROM::Name::AcornADFS); + } + if(target.has_dfs) { + request = request && ::ROM::Request(::ROM::Name::Acorn1770DFS); + } + if(target.has_ap6_rom) { + request = request && ::ROM::Request(::ROM::Name::PRESAdvancedPlus6); + } + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + set_rom(ROM::BASIC, roms.find(::ROM::Name::AcornBASICII)->second, false); + set_rom(ROM::OS, roms.find(::ROM::Name::AcornElectronMOS100)->second, false); + + /* + ROM slot mapping applied: + + * the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11; + * the DFS, if in use, occupies slot 1; + * the Pres ADFS, if in use, occupies slots 4 and 5; + * the Acorn ADFS, if in use, occupies slot 6; + * the AP6, if in use, occupies slot 15; and + * if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM. + */ + if(target.has_dfs || target.has_acorn_adfs || target.has_pres_adfs) { + plus3_ = std::make_unique(); + + if(target.has_dfs) { + set_rom(ROM::Slot0, roms.find(::ROM::Name::Acorn1770DFS)->second, true); + } if(target.has_pres_adfs) { - request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2); + set_rom(ROM::Slot4, roms.find(::ROM::Name::PRESADFSSlot1)->second, true); + set_rom(ROM::Slot5, roms.find(::ROM::Name::PRESADFSSlot2)->second, true); } if(target.has_acorn_adfs) { - request = request && ::ROM::Request(::ROM::Name::AcornADFS); + set_rom(ROM::Slot6, roms.find(::ROM::Name::AcornADFS)->second, true); } - if(target.has_dfs) { - request = request && ::ROM::Request(::ROM::Name::Acorn1770DFS); - } - if(target.has_ap6_rom) { - request = request && ::ROM::Request(::ROM::Name::PRESAdvancedPlus6); - } - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - set_rom(ROM::BASIC, roms.find(::ROM::Name::AcornBASICII)->second, false); - set_rom(ROM::OS, roms.find(::ROM::Name::AcornElectronMOS100)->second, false); + } + if(target.has_ap6_rom) { + set_rom(ROM::Slot15, roms.find(::ROM::Name::PRESAdvancedPlus6)->second, true); + } - /* - ROM slot mapping applied: - - * the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11; - * the DFS, if in use, occupies slot 1; - * the Pres ADFS, if in use, occupies slots 4 and 5; - * the Acorn ADFS, if in use, occupies slot 6; - * the AP6, if in use, occupies slot 15; and - * if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM. - */ - if(target.has_dfs || target.has_acorn_adfs || target.has_pres_adfs) { - plus3_ = std::make_unique(); - - if(target.has_dfs) { - set_rom(ROM::Slot0, roms.find(::ROM::Name::Acorn1770DFS)->second, true); - } - if(target.has_pres_adfs) { - set_rom(ROM::Slot4, roms.find(::ROM::Name::PRESADFSSlot1)->second, true); - set_rom(ROM::Slot5, roms.find(::ROM::Name::PRESADFSSlot2)->second, true); - } - if(target.has_acorn_adfs) { - set_rom(ROM::Slot6, roms.find(::ROM::Name::AcornADFS)->second, true); - } - } - if(target.has_ap6_rom) { - set_rom(ROM::Slot15, roms.find(::ROM::Name::PRESAdvancedPlus6)->second, true); - } - - if(target.has_sideways_ram) { - for(int c = 0; c < 16; c++) { - if(rom_inserted_[c]) continue; - if(c >= int(ROM::Keyboard) && c < int(ROM::BASIC)+1) continue; - set_sideways_ram(ROM(c)); - } - } - - insert_media(target.media); - - if(!target.loading_command.empty()) { - type_string(target.loading_command); - } - - if(target.should_shift_restart) { - shift_restart_counter_ = 1000000; - } - - if(has_scsi_bus) { - scsi_bus_.add_observer(this); - scsi_bus_.set_clocking_hint_observer(this); + if(target.has_sideways_ram) { + for(int c = 0; c < 16; c++) { + if(rom_inserted_[c]) continue; + if(c >= int(ROM::Keyboard) && c < int(ROM::BASIC)+1) continue; + set_sideways_ram(ROM(c)); } } - ~ConcreteMachine() { - audio_queue_.flush(); + insert_media(target.media); + + if(!target.loading_command.empty()) { + type_string(target.loading_command); } - void set_key_state(uint16_t key, bool isPressed) final { - switch(key) { - default: - if(isPressed) - key_states_[key >> 4] |= key&0xf; - else - key_states_[key >> 4] &= ~(key&0xf); - break; + if(target.should_shift_restart) { + shift_restart_counter_ = 1000000; + } - case KeyBreak: - m6502_.set_reset_line(isPressed); - break; + if(has_scsi_bus) { + scsi_bus_.add_observer(this); + scsi_bus_.set_clocking_hint_observer(this); + } + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + void set_key_state(uint16_t key, bool isPressed) final { + switch(key) { + default: + if(isPressed) + key_states_[key >> 4] |= key&0xf; + else + key_states_[key >> 4] &= ~(key&0xf); + break; + + case KeyBreak: + m6502_.set_reset_line(isPressed); + break; #define FuncShiftedKey(source, dest) \ - case source: \ - set_key_state(KeyFunc, isPressed); \ - set_key_state(dest, isPressed); \ - break; + case source: \ + set_key_state(KeyFunc, isPressed); \ + set_key_state(dest, isPressed); \ + break; - FuncShiftedKey(KeyF1, Key1); - FuncShiftedKey(KeyF2, Key2); - FuncShiftedKey(KeyF3, Key3); - FuncShiftedKey(KeyF4, Key4); - FuncShiftedKey(KeyF5, Key5); - FuncShiftedKey(KeyF6, Key6); - FuncShiftedKey(KeyF7, Key7); - FuncShiftedKey(KeyF8, Key8); - FuncShiftedKey(KeyF9, Key9); - FuncShiftedKey(KeyF0, Key0); + FuncShiftedKey(KeyF1, Key1); + FuncShiftedKey(KeyF2, Key2); + FuncShiftedKey(KeyF3, Key3); + FuncShiftedKey(KeyF4, Key4); + FuncShiftedKey(KeyF5, Key5); + FuncShiftedKey(KeyF6, Key6); + FuncShiftedKey(KeyF7, Key7); + FuncShiftedKey(KeyF8, Key8); + FuncShiftedKey(KeyF9, Key9); + FuncShiftedKey(KeyF0, Key0); #undef FuncShiftedKey + } + } + + void clear_all_keys() final { + memset(key_states_, 0, sizeof(key_states_)); + if(is_holding_shift_) set_key_state(KeyShift, true); + } + + bool insert_media(const Analyser::Static::Media &media) final { + if(!media.tapes.empty()) { + tape_.set_tape(media.tapes.front()); + } + set_use_fast_tape_hack(); + + if(!media.disks.empty() && plus3_) { + plus3_->set_disk(media.disks.front(), 0); + } + + ROM slot = ROM::Slot12; + for(std::shared_ptr cartridge : media.cartridges) { + const ROM first_slot_tried = slot; + while(rom_inserted_[int(slot)]) { + slot = ROM((int(slot) + 1) & 15); + if(slot == first_slot_tried) return false; + } + set_rom(slot, cartridge->get_segments().front().data, false); + } + + // TODO: allow this only at machine startup? + if(!media.mass_storage_devices.empty()) { + hard_drive_->set_storage(media.mass_storage_devices.front()); + } + + return !media.empty(); + } + + forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { + Cycles cycles{1}; + + if(address < 0x8000) { + cycles = video_.ram_delay(); + } else { + if((address & 0xff00) == 0xfe00) { + cycles = video_.io_delay(); } } - void clear_all_keys() final { - memset(key_states_, 0, sizeof(key_states_)); - if(is_holding_shift_) set_key_state(KeyShift, true); + if(const auto video_interrupts = video_.run_for(cycles); video_interrupts) { + signal_interrupt(video_interrupts); } - bool insert_media(const Analyser::Static::Media &media) final { - if(!media.tapes.empty()) { - tape_.set_tape(media.tapes.front()); - } - set_use_fast_tape_hack(); + cycles_since_audio_update_ += cycles; + if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); + tape_.run_for(cycles); - if(!media.disks.empty() && plus3_) { - plus3_->set_disk(media.disks.front(), 0); + if(typer_) typer_->run_for(cycles); + if(plus3_) plus3_->run_for(cycles * 4); + if(shift_restart_counter_) { + shift_restart_counter_ -= cycles.as(); + if(shift_restart_counter_ <= 0) { + shift_restart_counter_ = 0; + m6502_.set_power_on(true); + set_key_state(KeyShift, true); + is_holding_shift_ = true; } - - ROM slot = ROM::Slot12; - for(std::shared_ptr cartridge : media.cartridges) { - const ROM first_slot_tried = slot; - while(rom_inserted_[int(slot)]) { - slot = ROM((int(slot) + 1) & 15); - if(slot == first_slot_tried) return false; - } - set_rom(slot, cartridge->get_segments().front().data, false); - } - - // TODO: allow this only at machine startup? - if(!media.mass_storage_devices.empty()) { - hard_drive_->set_storage(media.mass_storage_devices.front()); - } - - return !media.empty(); } - forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - Cycles cycles{1}; + if constexpr (has_scsi_bus) { + if(scsi_is_clocked_) { + scsi_bus_.run_for(cycles); + } + } - if(address < 0x8000) { - cycles = video_.ram_delay(); + if(address < 0x8000) { + if(isReadOperation(operation)) { + *value = ram_[address]; } else { - if((address & 0xff00) == 0xfe00) { - cycles = video_.io_delay(); - } + ram_[address] = *value; } + } else { + switch(address & 0xff0f) { + case 0xfe00: + if(isReadOperation(operation)) { + *value = interrupt_status_; + interrupt_status_ &= ~PowerOnReset; + } else { + interrupt_control_ = (*value) & ~1; + evaluate_interrupts(); + } + break; + case 0xfe07: + if(!isReadOperation(operation)) { + // update speaker mode + bool new_speaker_is_enabled = (*value & 6) == 2; + if(new_speaker_is_enabled != speaker_is_enabled_) { + update_audio(); + sound_generator_.set_is_enabled(new_speaker_is_enabled); + speaker_is_enabled_ = new_speaker_is_enabled; + } - if(const auto video_interrupts = video_.run_for(cycles); video_interrupts) { - signal_interrupt(video_interrupts); - } + tape_.set_is_enabled((*value & 6) != 6); + tape_.set_is_in_input_mode((*value & 6) == 0); + tape_.set_is_running((*value & 0x40) ? true : false); - cycles_since_audio_update_ += cycles; - if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); - tape_.run_for(cycles); + caps_led_state_ = !!(*value & 0x80); + if(activity_observer_) + activity_observer_->set_led_status(caps_led, caps_led_state_); + } - if(typer_) typer_->run_for(cycles); - if(plus3_) plus3_->run_for(cycles * 4); - if(shift_restart_counter_) { - shift_restart_counter_ -= cycles.as(); - if(shift_restart_counter_ <= 0) { - shift_restart_counter_ = 0; - m6502_.set_power_on(true); - set_key_state(KeyShift, true); - is_holding_shift_ = true; - } - } + [[fallthrough]]; // fe07 contains the display mode. - if constexpr (has_scsi_bus) { - if(scsi_is_clocked_) { - scsi_bus_.run_for(cycles); - } - } - if(address < 0x8000) { - if(isReadOperation(operation)) { - *value = ram_[address]; - } else { - ram_[address] = *value; - } - } else { - switch(address & 0xff0f) { - case 0xfe00: - if(isReadOperation(operation)) { - *value = interrupt_status_; - interrupt_status_ &= ~PowerOnReset; - } else { - interrupt_control_ = (*value) & ~1; + case 0xfe02: case 0xfe03: + case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: + case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: + if(!isReadOperation(operation)) { + video_.write(address, *value); + } + break; + case 0xfe04: + if(isReadOperation(operation)) { + *value = tape_.get_data_register(); + tape_.clear_interrupts(Interrupt::ReceiveDataFull); + } else { + tape_.set_data_register(*value); + tape_.clear_interrupts(Interrupt::TransmitDataEmpty); + } + break; + case 0xfe05: + if(!isReadOperation(operation)) { + const uint8_t interruptDisable = (*value)&0xf0; + if( interruptDisable ) { + if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd; + if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock; + if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect; evaluate_interrupts(); - } - break; - case 0xfe07: - if(!isReadOperation(operation)) { - // update speaker mode - bool new_speaker_is_enabled = (*value & 6) == 2; - if(new_speaker_is_enabled != speaker_is_enabled_) { - update_audio(); - sound_generator_.set_is_enabled(new_speaker_is_enabled); - speaker_is_enabled_ = new_speaker_is_enabled; - } - tape_.set_is_enabled((*value & 6) != 6); - tape_.set_is_in_input_mode((*value & 6) == 0); - tape_.set_is_running((*value & 0x40) ? true : false); - - caps_led_state_ = !!(*value & 0x80); - if(activity_observer_) - activity_observer_->set_led_status(caps_led, caps_led_state_); - } - - [[fallthrough]]; // fe07 contains the display mode. - - - case 0xfe02: case 0xfe03: - case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: - case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: - if(!isReadOperation(operation)) { - video_.write(address, *value); - } - break; - case 0xfe04: - if(isReadOperation(operation)) { - *value = tape_.get_data_register(); - tape_.clear_interrupts(Interrupt::ReceiveDataFull); + // TODO: NMI } else { - tape_.set_data_register(*value); - tape_.clear_interrupts(Interrupt::TransmitDataEmpty); - } - break; - case 0xfe05: - if(!isReadOperation(operation)) { - const uint8_t interruptDisable = (*value)&0xf0; - if( interruptDisable ) { - if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd; - if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock; - if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect; - evaluate_interrupts(); + // Latch the paged ROM in case external hardware is being emulated. + active_rom_ = *value & 0xf; - // TODO: NMI - } else { - // Latch the paged ROM in case external hardware is being emulated. - active_rom_ = *value & 0xf; - - // apply the ULA's test - if(*value & 0x08) { - if(*value & 0x04) { - keyboard_is_active_ = false; - basic_is_active_ = false; - } else { - keyboard_is_active_ = !(*value & 0x02); - basic_is_active_ = !keyboard_is_active_; - } + // apply the ULA's test + if(*value & 0x08) { + if(*value & 0x04) { + keyboard_is_active_ = false; + basic_is_active_ = false; + } else { + keyboard_is_active_ = !(*value & 0x02); + basic_is_active_ = !keyboard_is_active_; } } } - break; - case 0xfe06: + } + break; + case 0xfe06: + if(!isReadOperation(operation)) { + update_audio(); + sound_generator_.set_divider(*value); + tape_.set_counter(*value); + } + break; + + case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: + if(plus3_ && (address&0x00f0) == 0x00c0) { + if(is_holding_shift_ && address == 0xfcc4) { + is_holding_shift_ = false; + set_key_state(KeyShift, false); + } + if(isReadOperation(operation)) + *value = plus3_->read(address); + else + plus3_->write(address, *value); + } + break; + case 0xfc00: + if(plus3_ && (address&0x00f0) == 0x00c0) { if(!isReadOperation(operation)) { - update_audio(); - sound_generator_.set_divider(*value); - tape_.set_counter(*value); - } - break; + plus3_->set_control_register(*value); + } else *value = 1; + } - case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: - if(plus3_ && (address&0x00f0) == 0x00c0) { - if(is_holding_shift_ && address == 0xfcc4) { - is_holding_shift_ = false; - set_key_state(KeyShift, false); - } - if(isReadOperation(operation)) - *value = plus3_->read(address); - else - plus3_->write(address, *value); - } - break; - case 0xfc00: - if(plus3_ && (address&0x00f0) == 0x00c0) { - if(!isReadOperation(operation)) { - plus3_->set_control_register(*value); - } else *value = 1; - } - - if(has_scsi_bus && (address&0x00f0) == 0x0040) { - scsi_acknowledge_ = true; - if(!isReadOperation(operation)) { - scsi_data_ = *value; - push_scsi_output(); - } else { - *value = SCSI::data_lines(scsi_bus_.get_state()); - push_scsi_output(); - } - } - break; - case 0xfc03: - if(has_scsi_bus && (address&0x00f0) == 0x0040) { - scsi_interrupt_state_ = false; - scsi_interrupt_mask_ = *value & 1; - evaluate_interrupts(); - } - break; - case 0xfc01: - if(has_scsi_bus && (address&0x00f0) == 0x0040 && isReadOperation(operation)) { - // Status byte is: - // - // b7: SCSI C/D - // b6: SCSI I/O - // b5: SCSI REQ - // b4: interrupt flag - // b3: 0 - // b2: 0 - // b1: SCSI BSY - // b0: SCSI MSG - const auto state = scsi_bus_.get_state(); - *value = - (state & SCSI::Line::Control ? 0x80 : 0x00) | - (state & SCSI::Line::Input ? 0x40 : 0x00) | - (state & SCSI::Line::Request ? 0x20 : 0x00) | - ((scsi_interrupt_state_ && scsi_interrupt_mask_) ? 0x10 : 0x00) | - (state & SCSI::Line::Busy ? 0x02 : 0x00) | - (state & SCSI::Line::Message ? 0x01 : 0x00); - - // Empirical guess: this is also the trigger to affect busy/request/acknowledge - // signalling. Maybe? - if(scsi_select_ && scsi_bus_.get_state() & SCSI::Line::Busy) { - scsi_select_ = false; - push_scsi_output(); - } - } - break; - case 0xfc02: - if(has_scsi_bus && (address&0x00f0) == 0x0040) { - scsi_select_ = true; + if(has_scsi_bus && (address&0x00f0) == 0x0040) { + scsi_acknowledge_ = true; + if(!isReadOperation(operation)) { + scsi_data_ = *value; + push_scsi_output(); + } else { + *value = SCSI::data_lines(scsi_bus_.get_state()); push_scsi_output(); } - break; + } + break; + case 0xfc03: + if(has_scsi_bus && (address&0x00f0) == 0x0040) { + scsi_interrupt_state_ = false; + scsi_interrupt_mask_ = *value & 1; + evaluate_interrupts(); + } + break; + case 0xfc01: + if(has_scsi_bus && (address&0x00f0) == 0x0040 && isReadOperation(operation)) { + // Status byte is: + // + // b7: SCSI C/D + // b6: SCSI I/O + // b5: SCSI REQ + // b4: interrupt flag + // b3: 0 + // b2: 0 + // b1: SCSI BSY + // b0: SCSI MSG + const auto state = scsi_bus_.get_state(); + *value = + (state & SCSI::Line::Control ? 0x80 : 0x00) | + (state & SCSI::Line::Input ? 0x40 : 0x00) | + (state & SCSI::Line::Request ? 0x20 : 0x00) | + ((scsi_interrupt_state_ && scsi_interrupt_mask_) ? 0x10 : 0x00) | + (state & SCSI::Line::Busy ? 0x02 : 0x00) | + (state & SCSI::Line::Message ? 0x01 : 0x00); - // SCSI locations: - // - // fc40: data, read and write - // fc41: status read - // fc42: select write - // fc43: interrupt latch - // - // - // Interrupt latch is: - // - // b0: enable or disable IRQ on REQ - // (and, possibly, writing to the latch acknowledges?) + // Empirical guess: this is also the trigger to affect busy/request/acknowledge + // signalling. Maybe? + if(scsi_select_ && scsi_bus_.get_state() & SCSI::Line::Busy) { + scsi_select_ = false; + push_scsi_output(); + } + } + break; + case 0xfc02: + if(has_scsi_bus && (address&0x00f0) == 0x0040) { + scsi_select_ = true; + push_scsi_output(); + } + break; - default: - if(address >= 0xc000) { - if(isReadOperation(operation)) { - if( - use_fast_tape_hack_ && - (operation == CPU::MOS6502::BusOperation::ReadOpcode) && - ( - (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 - (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling - (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM - (address == 0xfa51) || (address == 0xfa52) || // pathway. + // SCSI locations: + // + // fc40: data, read and write + // fc41: status read + // fc42: select write + // fc43: interrupt latch + // + // + // Interrupt latch is: + // + // b0: enable or disable IRQ on REQ + // (and, possibly, writing to the latch acknowledges?) - (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be - // dispatched; we can check whether it would be call 14 - // (i.e. read byte) and, if so, whether the OS was about to - // issue a read byte call to a ROM despite the tape - // FS being selected. If so then this is a get byte that - // we should service synthetically. Put the byte into Y - // and set A to zero to report that action was taken, then - // allow the PC read to return an RTS. - ) - ) { - const auto service_call = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); - if(address == 0xf0a8) { - if(!ram_[0x247] && service_call == 14) { - tape_.set_delegate(nullptr); + default: + if(address >= 0xc000) { + if(isReadOperation(operation)) { + if( + use_fast_tape_hack_ && + (operation == CPU::MOS6502::BusOperation::ReadOpcode) && + ( + (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 + (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling + (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM + (address == 0xfa51) || (address == 0xfa52) || // pathway. - int cycles_left_while_plausibly_in_data = 50; - tape_.clear_interrupts(Interrupt::ReceiveDataFull); - while(!tape_.get_tape()->is_at_end()) { - tape_.run_for_input_pulse(); - --cycles_left_while_plausibly_in_data; - if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false; - if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) && - (fast_load_is_in_data_ || tape_.get_data_register() == 0x2a) - ) break; - } - tape_.set_delegate(this); - tape_.clear_interrupts(Interrupt::ReceiveDataFull); - interrupt_status_ |= tape_.get_interrupt_status(); + (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be + // dispatched; we can check whether it would be call 14 + // (i.e. read byte) and, if so, whether the OS was about to + // issue a read byte call to a ROM despite the tape + // FS being selected. If so then this is a get byte that + // we should service synthetically. Put the byte into Y + // and set A to zero to report that action was taken, then + // allow the PC read to return an RTS. + ) + ) { + const auto service_call = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); + if(address == 0xf0a8) { + if(!ram_[0x247] && service_call == 14) { + tape_.set_delegate(nullptr); - fast_load_is_in_data_ = true; - m6502_.set_value_of(CPU::MOS6502::Register::A, 0); - m6502_.set_value_of(CPU::MOS6502::Register::Y, tape_.get_data_register()); - *value = 0x60; // 0x60 is RTS + int cycles_left_while_plausibly_in_data = 50; + tape_.clear_interrupts(Interrupt::ReceiveDataFull); + while(!tape_.get_tape()->is_at_end()) { + tape_.run_for_input_pulse(); + --cycles_left_while_plausibly_in_data; + if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false; + if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) && + (fast_load_is_in_data_ || tape_.get_data_register() == 0x2a) + ) break; } - else *value = os_[address & 16383]; + tape_.set_delegate(this); + tape_.clear_interrupts(Interrupt::ReceiveDataFull); + interrupt_status_ |= tape_.get_interrupt_status(); + + fast_load_is_in_data_ = true; + m6502_.set_value_of(CPU::MOS6502::Register::A, 0); + m6502_.set_value_of(CPU::MOS6502::Register::Y, tape_.get_data_register()); + *value = 0x60; // 0x60 is RTS } - else *value = 0xea; - } else { - *value = os_[address & 16383]; + else *value = os_[address & 16383]; } - } - } else { - if(isReadOperation(operation)) { - *value = roms_[active_rom_][address & 16383]; - if(keyboard_is_active_) { - *value &= 0xf0; - for(int address_line = 0; address_line < 14; address_line++) { - if(!(address&(1 << address_line))) *value |= key_states_[address_line]; - } - } - if(basic_is_active_) { - *value &= roms_[int(ROM::BASIC)][address & 16383]; - } - } else if(rom_write_masks_[active_rom_]) { - roms_[active_rom_][address & 16383] = *value; + else *value = 0xea; + } else { + *value = os_[address & 16383]; } } - break; - } - } - - return cycles; - } - - void flush_output(int outputs) final { - if(outputs & Output::Audio) { - update_audio(); - audio_queue_.perform(); - } - } - - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - video_.set_scan_target(scan_target); - } - - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return video_.get_scaled_scan_status(); - } - - void set_display_type(Outputs::Display::DisplayType display_type) final { - video_.set_display_type(display_type); - } - - Outputs::Display::DisplayType get_display_type() const final { - return video_.get_display_type(); - } - - Outputs::Speaker::Speaker *get_speaker() final { - return &speaker_; - } - - void run_for(const Cycles cycles) final { - m6502_.run_for(cycles); - } - - void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double) final { - // Release acknowledge when request is released. - if(scsi_acknowledge_ && !(new_state & SCSI::Line::Request)) { - scsi_acknowledge_ = false; - push_scsi_output(); - } - - // Output occurs only while SCSI::Line::Input is inactive; therefore a change - // in that line affects what's on the bus. - if(((new_state^previous_bus_state_)&SCSI::Line::Input)) { - push_scsi_output(); - } - - scsi_interrupt_state_ |= (new_state^previous_bus_state_)&new_state & SCSI::Line::Request; - previous_bus_state_ = new_state; - evaluate_interrupts(); - } - - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) final { - scsi_is_clocked_ = preference != ClockingHint::Preference::None; - } - - void tape_did_change_interrupt_status(Tape *) final { - interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); - evaluate_interrupts(); - } - - HalfCycles get_typer_delay(const std::string &text) const final { - if(!m6502_.get_is_resetting()) { - return Cycles(0); - } - - // Add a longer delay for a command at reset that involves pressing a modifier; - // empirically this seems to be a requirement, in order to avoid a collision with - // the system's built-in modifier-at-startup test (e.g. to perform shift+break). - CharacterMapper test_mapper; - const uint16_t *const sequence = test_mapper.sequence_for_character(text[0]); - return is_modifier(Key(sequence[0])) ? Cycles(1'000'000) : Cycles(750'000); - } - - HalfCycles get_typer_frequency() const final { - return Cycles(60'000); - } - - void type_string(const std::string &string) final { - Utility::TypeRecipient::add_typer(string); - } - - bool can_type(char c) const final { - return Utility::TypeRecipient::can_type(c); - } - - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; - } - - // MARK: - Configuration options. - std::unique_ptr get_options() const final { - auto options = std::make_unique(Configurable::OptionsType::UserFriendly); - options->output = get_video_signal_configurable(); - options->quickload = allow_fast_tape_hack_; - return options; - } - - void set_options(const std::unique_ptr &str) final { - const auto options = dynamic_cast(str.get()); - - set_video_signal_configurable(options->output); - allow_fast_tape_hack_ = options->quickload; - set_use_fast_tape_hack(); - } - - // MARK: - Activity Source - void set_activity_observer(Activity::Observer *observer) final { - activity_observer_ = observer; - if(activity_observer_) { - activity_observer_->register_led(caps_led, Activity::Observer::LEDPresentation::Persistent); - activity_observer_->set_led_status(caps_led, caps_led_state_); - } - - if(plus3_) { - plus3_->set_activity_observer(observer); - } - - if(has_scsi_bus) { - scsi_bus_.set_activity_observer(observer); - } - } - - private: - enum class ROM { - Slot0 = 0, - Slot1, Slot2, Slot3, - Slot4, Slot5, Slot6, Slot7, - - Keyboard = 8, Slot9, - BASIC = 10, Slot11, - - Slot12, Slot13, Slot14, Slot15, - - OS, DFS, - ADFS1, ADFS2 - }; - - /*! - Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot - is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. - */ - void set_rom(ROM slot, const std::vector &data, bool is_writeable) { - uint8_t *target = nullptr; - switch(slot) { - case ROM::DFS: dfs_ = data; return; - case ROM::ADFS1: adfs1_ = data; return; - case ROM::ADFS2: adfs2_ = data; return; - - case ROM::OS: target = os_; break; - default: - target = roms_[int(slot)]; - rom_write_masks_[int(slot)] = is_writeable; + } else { + if(isReadOperation(operation)) { + *value = roms_[active_rom_][address & 16383]; + if(keyboard_is_active_) { + *value &= 0xf0; + for(int address_line = 0; address_line < 14; address_line++) { + if(!(address&(1 << address_line))) *value |= key_states_[address_line]; + } + } + if(basic_is_active_) { + *value &= roms_[int(ROM::BASIC)][address & 16383]; + } + } else if(rom_write_masks_[active_rom_]) { + roms_[active_rom_][address & 16383] = *value; + } + } break; } - - // Copy in, with mirroring. - std::size_t rom_ptr = 0; - while(rom_ptr < 16384) { - std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size()); - std::memcpy(&target[rom_ptr], data.data(), size_to_copy); - rom_ptr += size_to_copy; - } - - if(int(slot) < 16) { - rom_inserted_[int(slot)] = true; - } } - /*! - Enables @c slot as sideways RAM; ensures that it does not currently contain a valid ROM signature. - */ - void set_sideways_ram(ROM slot) { - std::memset(roms_[int(slot)], 0xff, 16*1024); - if(int(slot) < 16) { - rom_inserted_[int(slot)] = true; - rom_write_masks_[int(slot)] = true; - } + return cycles; + } + + void flush_output(int outputs) final { + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); + } + } + + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + video_.set_scan_target(scan_target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return video_.get_scaled_scan_status(); + } + + void set_display_type(Outputs::Display::DisplayType display_type) final { + video_.set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return video_.get_display_type(); + } + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + void run_for(const Cycles cycles) final { + m6502_.run_for(cycles); + } + + void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double) final { + // Release acknowledge when request is released. + if(scsi_acknowledge_ && !(new_state & SCSI::Line::Request)) { + scsi_acknowledge_ = false; + push_scsi_output(); } - // MARK: - Work deferral updates. - inline void update_audio() { - speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider))); + // Output occurs only while SCSI::Line::Input is inactive; therefore a change + // in that line affects what's on the bus. + if(((new_state^previous_bus_state_)&SCSI::Line::Input)) { + push_scsi_output(); } - inline void signal_interrupt(uint8_t interrupt) { - if(!interrupt) { - return; - } - interrupt_status_ |= interrupt; - evaluate_interrupts(); + scsi_interrupt_state_ |= (new_state^previous_bus_state_)&new_state & SCSI::Line::Request; + previous_bus_state_ = new_state; + evaluate_interrupts(); + } + + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) final { + scsi_is_clocked_ = preference != ClockingHint::Preference::None; + } + + void tape_did_change_interrupt_status(Tape *) final { + interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status(); + evaluate_interrupts(); + } + + HalfCycles get_typer_delay(const std::string &text) const final { + if(!m6502_.get_is_resetting()) { + return Cycles(0); } - inline void clear_interrupt(Interrupt interrupt) { - interrupt_status_ &= ~interrupt; - evaluate_interrupts(); + // Add a longer delay for a command at reset that involves pressing a modifier; + // empirically this seems to be a requirement, in order to avoid a collision with + // the system's built-in modifier-at-startup test (e.g. to perform shift+break). + CharacterMapper test_mapper; + const uint16_t *const sequence = test_mapper.sequence_for_character(text[0]); + return is_modifier(Key(sequence[0])) ? Cycles(1'000'000) : Cycles(750'000); + } + + HalfCycles get_typer_frequency() const final { + return Cycles(60'000); + } + + void type_string(const std::string &string) final { + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) const final { + return Utility::TypeRecipient::can_type(c); + } + + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + + // MARK: - Configuration options. + std::unique_ptr get_options() const final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->quickload = allow_fast_tape_hack_; + return options; + } + + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + + set_video_signal_configurable(options->output); + allow_fast_tape_hack_ = options->quickload; + set_use_fast_tape_hack(); + } + + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) final { + activity_observer_ = observer; + if(activity_observer_) { + activity_observer_->register_led(caps_led, Activity::Observer::LEDPresentation::Persistent); + activity_observer_->set_led_status(caps_led, caps_led_state_); } - inline void evaluate_interrupts() { - if(interrupt_status_ & interrupt_control_) { - interrupt_status_ |= 1; - } else { - interrupt_status_ &= ~1; - } - - if constexpr (has_scsi_bus) { - m6502_.set_irq_line((scsi_interrupt_state_ && scsi_interrupt_mask_) | (interrupt_status_ & 1)); - } else { - m6502_.set_irq_line(interrupt_status_ & 1); - } + if(plus3_) { + plus3_->set_activity_observer(observer); } - CPU::MOS6502::Processor m6502_; - - // Things that directly constitute the memory map. - uint8_t roms_[16][16384]; - bool rom_inserted_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; - bool rom_write_masks_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; - uint8_t os_[16384], ram_[32768]; - std::vector dfs_, adfs1_, adfs2_; - - // Paging - int active_rom_ = int(ROM::Slot0); - bool keyboard_is_active_ = false; - bool basic_is_active_ = false; - - // Interrupt and keyboard state - uint8_t interrupt_status_ = Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80; - uint8_t interrupt_control_ = 0; - uint8_t key_states_[14]; - Electron::KeyboardMapper keyboard_mapper_; - - // Counters related to simultaneous subsystems - Cycles cycles_since_audio_update_ = 0; - - // Tape - Tape tape_; - bool use_fast_tape_hack_ = false; - bool allow_fast_tape_hack_ = false; - void set_use_fast_tape_hack() { - use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_.has_tape(); + if(has_scsi_bus) { + scsi_bus_.set_activity_observer(observer); } - bool fast_load_is_in_data_ = false; + } - // Disk - std::unique_ptr plus3_; - bool is_holding_shift_ = false; - int shift_restart_counter_ = 0; +private: + enum class ROM { + Slot0 = 0, + Slot1, Slot2, Slot3, + Slot4, Slot5, Slot6, Slot7, - // Hard drive. - SCSI::Bus scsi_bus_; - SCSI::Target::Target hard_drive_; - SCSI::BusState previous_bus_state_ = SCSI::DefaultBusState; - const size_t scsi_device_ = 0; - uint8_t scsi_data_ = 0; - bool scsi_select_ = false; - bool scsi_acknowledge_ = false; - bool scsi_is_clocked_ = false; - bool scsi_interrupt_state_ = false; - bool scsi_interrupt_mask_ = false; - void push_scsi_output() { - scsi_bus_.set_device_output(scsi_device_, - (scsi_bus_.get_state()&SCSI::Line::Input ? 0 : scsi_data_) | - (scsi_select_ ? SCSI::Line::SelectTarget : 0) | - (scsi_acknowledge_ ? SCSI::Line::Acknowledge : 0) - ); + Keyboard = 8, Slot9, + BASIC = 10, Slot11, + + Slot12, Slot13, Slot14, Slot15, + + OS, DFS, + ADFS1, ADFS2 + }; + + /*! + Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot + is enabled: it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM. + */ + void set_rom(ROM slot, const std::vector &data, bool is_writeable) { + uint8_t *target = nullptr; + switch(slot) { + case ROM::DFS: dfs_ = data; return; + case ROM::ADFS1: adfs1_ = data; return; + case ROM::ADFS2: adfs2_ = data; return; + + case ROM::OS: target = os_; break; + default: + target = roms_[int(slot)]; + rom_write_masks_[int(slot)] = is_writeable; + break; } - // Outputs - VideoOutput video_; + // Copy in, with mirroring. + std::size_t rom_ptr = 0; + while(rom_ptr < 16384) { + std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size()); + std::memcpy(&target[rom_ptr], data.data(), size_to_copy); + rom_ptr += size_to_copy; + } - Concurrency::AsyncTaskQueue audio_queue_; - SoundGenerator sound_generator_; - Outputs::Speaker::PullLowpass speaker_; + if(int(slot) < 16) { + rom_inserted_[int(slot)] = true; + } + } - bool speaker_is_enabled_ = false; + /*! + Enables @c slot as sideways RAM; ensures that it does not currently contain a valid ROM signature. + */ + void set_sideways_ram(ROM slot) { + std::memset(roms_[int(slot)], 0xff, 16*1024); + if(int(slot) < 16) { + rom_inserted_[int(slot)] = true; + rom_write_masks_[int(slot)] = true; + } + } - // MARK: - Caps Lock status and the activity observer. - const std::string caps_led = "CAPS"; - bool caps_led_state_ = false; - Activity::Observer *activity_observer_ = nullptr; + // MARK: - Work deferral updates. + inline void update_audio() { + speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider))); + } + + inline void signal_interrupt(uint8_t interrupt) { + if(!interrupt) { + return; + } + interrupt_status_ |= interrupt; + evaluate_interrupts(); + } + + inline void clear_interrupt(Interrupt interrupt) { + interrupt_status_ &= ~interrupt; + evaluate_interrupts(); + } + + inline void evaluate_interrupts() { + if(interrupt_status_ & interrupt_control_) { + interrupt_status_ |= 1; + } else { + interrupt_status_ &= ~1; + } + + if constexpr (has_scsi_bus) { + m6502_.set_irq_line((scsi_interrupt_state_ && scsi_interrupt_mask_) | (interrupt_status_ & 1)); + } else { + m6502_.set_irq_line(interrupt_status_ & 1); + } + } + + CPU::MOS6502::Processor m6502_; + + // Things that directly constitute the memory map. + uint8_t roms_[16][16384]; + bool rom_inserted_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; + bool rom_write_masks_[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; + uint8_t os_[16384], ram_[32768]; + std::vector dfs_, adfs1_, adfs2_; + + // Paging + int active_rom_ = int(ROM::Slot0); + bool keyboard_is_active_ = false; + bool basic_is_active_ = false; + + // Interrupt and keyboard state + uint8_t interrupt_status_ = Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80; + uint8_t interrupt_control_ = 0; + uint8_t key_states_[14]; + Electron::KeyboardMapper keyboard_mapper_; + + // Counters related to simultaneous subsystems + Cycles cycles_since_audio_update_ = 0; + + // Tape + Tape tape_; + bool use_fast_tape_hack_ = false; + bool allow_fast_tape_hack_ = false; + void set_use_fast_tape_hack() { + use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_.has_tape(); + } + bool fast_load_is_in_data_ = false; + + // Disk + std::unique_ptr plus3_; + bool is_holding_shift_ = false; + int shift_restart_counter_ = 0; + + // Hard drive. + SCSI::Bus scsi_bus_; + SCSI::Target::Target hard_drive_; + SCSI::BusState previous_bus_state_ = SCSI::DefaultBusState; + const size_t scsi_device_ = 0; + uint8_t scsi_data_ = 0; + bool scsi_select_ = false; + bool scsi_acknowledge_ = false; + bool scsi_is_clocked_ = false; + bool scsi_interrupt_state_ = false; + bool scsi_interrupt_mask_ = false; + void push_scsi_output() { + scsi_bus_.set_device_output(scsi_device_, + (scsi_bus_.get_state()&SCSI::Line::Input ? 0 : scsi_data_) | + (scsi_select_ ? SCSI::Line::SelectTarget : 0) | + (scsi_acknowledge_ ? SCSI::Line::Acknowledge : 0) + ); + } + + // Outputs + VideoOutput video_; + + Concurrency::AsyncTaskQueue audio_queue_; + SoundGenerator sound_generator_; + Outputs::Speaker::PullLowpass speaker_; + + bool speaker_is_enabled_ = false; + + // MARK: - Caps Lock status and the activity observer. + const std::string caps_led = "CAPS"; + bool caps_led_state_ = false; + Activity::Observer *activity_observer_ = nullptr; }; } diff --git a/Machines/Acorn/Electron/Plus3.hpp b/Machines/Acorn/Electron/Plus3.hpp index be7335ad1..c76dfa4bf 100644 --- a/Machines/Acorn/Electron/Plus3.hpp +++ b/Machines/Acorn/Electron/Plus3.hpp @@ -14,19 +14,19 @@ namespace Electron { class Plus3 final : public WD::WD1770 { - public: - Plus3(); +public: + Plus3(); - void set_disk(std::shared_ptr disk, size_t drive); - void set_control_register(uint8_t control); - void set_activity_observer(Activity::Observer *observer); + void set_disk(std::shared_ptr, size_t drive); + void set_control_register(uint8_t control); + void set_activity_observer(Activity::Observer *); - private: - void set_control_register(uint8_t control, uint8_t changes); - uint8_t last_control_ = 0; +private: + void set_control_register(uint8_t control, uint8_t changes); + uint8_t last_control_ = 0; - void set_motor_on(bool on) override; - std::string drive_name(size_t drive); + void set_motor_on(bool) override; + std::string drive_name(size_t); }; } diff --git a/Machines/Acorn/Electron/SoundGenerator.hpp b/Machines/Acorn/Electron/SoundGenerator.hpp index aa3ee82d7..53a2653b7 100644 --- a/Machines/Acorn/Electron/SoundGenerator.hpp +++ b/Machines/Acorn/Electron/SoundGenerator.hpp @@ -14,26 +14,25 @@ namespace Electron { class SoundGenerator: public ::Outputs::Speaker::BufferSource { - public: - SoundGenerator(Concurrency::AsyncTaskQueue &audio_queue); +public: + SoundGenerator(Concurrency::AsyncTaskQueue &); - void set_divider(uint8_t divider); + void set_divider(uint8_t); + void set_is_enabled(bool); - void set_is_enabled(bool is_enabled); + static constexpr unsigned int clock_rate_divider = 8; - static constexpr unsigned int clock_rate_divider = 8; + // For BufferSource. + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *); + void set_sample_volume_range(std::int16_t range); - // For BufferSource. - template - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); - void set_sample_volume_range(std::int16_t range); - - private: - Concurrency::AsyncTaskQueue &audio_queue_; - unsigned int counter_ = 0; - unsigned int divider_ = 0; - bool is_enabled_ = false; - unsigned int volume_ = 0; +private: + Concurrency::AsyncTaskQueue &audio_queue_; + unsigned int counter_ = 0; + unsigned int divider_ = 0; + bool is_enabled_ = false; + unsigned int volume_ = 0; }; } diff --git a/Machines/Acorn/Electron/Tape.hpp b/Machines/Acorn/Electron/Tape.hpp index bbf63dd05..4ab9d94ee 100644 --- a/Machines/Acorn/Electron/Tape.hpp +++ b/Machines/Acorn/Electron/Tape.hpp @@ -20,56 +20,56 @@ namespace Electron { class Tape: public Storage::Tape::TapePlayer, public Storage::Tape::Acorn::Shifter::Delegate { - public: - Tape(); +public: + Tape(); - void run_for(const Cycles cycles); - using Storage::Tape::TapePlayer::run_for; + void run_for(const Cycles cycles); + using Storage::Tape::TapePlayer::run_for; - uint8_t get_data_register(); - void set_data_register(uint8_t value); - void set_counter(uint8_t value); + uint8_t get_data_register(); + void set_data_register(uint8_t value); + void set_counter(uint8_t value); - inline uint8_t get_interrupt_status() { return interrupt_status_; } - void clear_interrupts(uint8_t interrupts); + inline uint8_t get_interrupt_status() { return interrupt_status_; } + void clear_interrupts(uint8_t interrupts); - class Delegate { - public: - virtual void tape_did_change_interrupt_status(Tape *tape) = 0; - }; - inline void set_delegate(Delegate *delegate) { delegate_ = delegate; } + class Delegate { + public: + virtual void tape_did_change_interrupt_status(Tape *tape) = 0; + }; + inline void set_delegate(Delegate *delegate) { delegate_ = delegate; } - inline void set_is_running(bool is_running) { is_running_ = is_running; } - inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } - void set_is_in_input_mode(bool is_in_input_mode); + inline void set_is_running(bool is_running) { is_running_ = is_running; } + inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } + void set_is_in_input_mode(bool is_in_input_mode); - void acorn_shifter_output_bit(int value); + void acorn_shifter_output_bit(int value); - private: - void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); - inline void push_tape_bit(uint16_t bit); - inline void get_next_tape_pulse(); +private: + void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); + inline void push_tape_bit(uint16_t bit); + inline void get_next_tape_pulse(); - struct { - int minimum_bits_until_full = 0; - } input_; - struct { - unsigned int cycles_into_pulse = 0; - unsigned int bits_remaining_until_empty = 0; - } output_; + struct { + int minimum_bits_until_full = 0; + } input_; + struct { + unsigned int cycles_into_pulse = 0; + unsigned int bits_remaining_until_empty = 0; + } output_; - bool is_running_ = false; - bool is_enabled_ = false; - bool is_in_input_mode_ = false; + bool is_running_ = false; + bool is_enabled_ = false; + bool is_in_input_mode_ = false; - inline void evaluate_interrupts(); - uint16_t data_register_ = 0; + inline void evaluate_interrupts(); + uint16_t data_register_ = 0; - uint8_t interrupt_status_ = 0; - uint8_t last_posted_interrupt_status_ = 0; - Delegate *delegate_ = nullptr; + uint8_t interrupt_status_ = 0; + uint8_t last_posted_interrupt_status_ = 0; + Delegate *delegate_ = nullptr; - ::Storage::Tape::Acorn::Shifter shifter_; + ::Storage::Tape::Acorn::Shifter shifter_; }; } diff --git a/Machines/Acorn/Electron/Video.hpp b/Machines/Acorn/Electron/Video.hpp index 98993f4ff..a79458786 100644 --- a/Machines/Acorn/Electron/Video.hpp +++ b/Machines/Acorn/Electron/Video.hpp @@ -24,160 +24,160 @@ namespace Electron { is accessing it the CPU may not. */ class VideoOutput { - public: - /*! - Instantiates a VideoOutput that will read its pixels from @c memory. +public: + /*! + Instantiates a VideoOutput that will read its pixels from @c memory. - The pointer supplied should be to address 0 in the unexpanded Electron's memory map. - */ - VideoOutput(const uint8_t *memory); + The pointer supplied should be to address 0 in the unexpanded Electron's memory map. + */ + VideoOutput(const uint8_t *memory); - /// Sets the destination for output. - void set_scan_target(Outputs::Display::ScanTarget *scan_target); + /// Sets the destination for output. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); - /// Gets the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const; + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const; - /// Sets the type of output. - void set_display_type(Outputs::Display::DisplayType); + /// Sets the type of output. + void set_display_type(Outputs::Display::DisplayType); - /// Gets the type of output. - Outputs::Display::DisplayType get_display_type() const; + /// Gets the type of output. + Outputs::Display::DisplayType get_display_type() const; - /// Produces the next @c cycles of video output. - /// - /// @returns a bit mask of all interrupts triggered. - uint8_t run_for(const Cycles cycles); + /// Produces the next @c cycles of video output. + /// + /// @returns a bit mask of all interrupts triggered. + uint8_t run_for(const Cycles cycles); - /// @returns The number of 2Mhz cycles that will pass before completion of an attempted - /// IO [/1Mhz] access that is first signalled in the upcoming cycle. - Cycles io_delay() { - return 2 + ((h_count_ >> 3)&1); + /// @returns The number of 2Mhz cycles that will pass before completion of an attempted + /// IO [/1Mhz] access that is first signalled in the upcoming cycle. + Cycles io_delay() { + return 2 + ((h_count_ >> 3)&1); + } + + /// @returns The number of 2Mhz cycles that will pass before completion of an attempted + /// RAM access that is first signalled in the upcoming cycle. + Cycles ram_delay() { + if(!mode_40_ && !in_blank()) { + return 2 + ((h_active - h_count_) >> 3); } + return io_delay(); + } - /// @returns The number of 2Mhz cycles that will pass before completion of an attempted - /// RAM access that is first signalled in the upcoming cycle. - Cycles ram_delay() { - if(!mode_40_ && !in_blank()) { - return 2 + ((h_active - h_count_) >> 3); - } - return io_delay(); + /*! + Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, + @c get_cycles_until_next_ram_availability and @c get_memory_access_range. + */ + void write(int address, uint8_t value); + + /*! + @returns the number of cycles after (final cycle of last run_for batch + @c from_time) + before the video circuits will allow the CPU to access RAM. + */ + unsigned int get_cycles_until_next_ram_availability(int from_time); + +private: + const uint8_t *ram_ = nullptr; + + // CRT output + enum class OutputStage { + Sync, Blank, Pixels, ColourBurst, + }; + OutputStage output_ = OutputStage::Blank; + int output_length_ = 0; + int screen_pitch_ = 0; + + uint8_t *current_output_target_ = nullptr; + uint8_t *initial_output_target_ = nullptr; + int current_output_divider_ = 1; + Outputs::CRT::CRT crt_; + + // Palettes. + uint8_t palette_[8]{}; + uint8_t palette1bpp_[2]{}; + uint8_t palette2bpp_[4]{}; + uint8_t palette4bpp_[16]{}; + + template + uint8_t channel() { + if constexpr (source_bit < target_bit) { + return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit); + } else { + return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit); } + } - /*! - Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, - @c get_cycles_until_next_ram_availability and @c get_memory_access_range. - */ - void write(int address, uint8_t value); + template + uint8_t palette_entry() { + return channel() | channel() | channel(); + } - /*! - @returns the number of cycles after (final cycle of last run_for batch + @c from_time) - before the video circuits will allow the CPU to access RAM. - */ - unsigned int get_cycles_until_next_ram_availability(int from_time); + // User-selected base address; constrained to a 64-byte boundary by the setter. + uint16_t screen_base_ = 0; - private: - const uint8_t *ram_ = nullptr; + // Parameters implied by mode selection. + uint16_t mode_base_ = 0; + bool mode_40_ = true; + bool mode_text_ = false; + enum class Bpp { + One = 1, Two = 2, Four = 4 + } mode_bpp_ = Bpp::One; - // CRT output - enum class OutputStage { - Sync, Blank, Pixels, ColourBurst, - }; - OutputStage output_ = OutputStage::Blank; - int output_length_ = 0; - int screen_pitch_ = 0; + // Frame position. + int v_count_ = 0; + int h_count_ = 0; + bool field_ = true; - uint8_t *current_output_target_ = nullptr; - uint8_t *initial_output_target_ = nullptr; - int current_output_divider_ = 1; - Outputs::CRT::CRT crt_; + // Current working address. + uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row. + uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line. + int char_row_ = 0; // Character row; 0–9 in text mode, 0–7 in graphics. - // Palettes. - uint8_t palette_[8]{}; - uint8_t palette1bpp_[2]{}; - uint8_t palette2bpp_[4]{}; - uint8_t palette4bpp_[16]{}; + // Sync states. + bool vsync_int_ = false; // True => vsync active. + bool hsync_int_ = false; // True => hsync active. - template - uint8_t channel() { - if constexpr (source_bit < target_bit) { - return (palette_[index] << (target_bit - source_bit)) & (1 << target_bit); - } else { - return (palette_[index] >> (source_bit - target_bit)) & (1 << target_bit); - } - } + // Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all + // divisible by 8, so it's safe to count time with a 2Mhz input. + static constexpr int h_active = 640; + static constexpr int hsync_start = 768; + static constexpr int hsync_end = 832; + static constexpr int h_reset_addr = 1016; + static constexpr int h_total = 1024; // Minor digression from the FPGA original here; + // in this implementation the value is tested + // _after_ position increment rather than before/instead. + // So it needs to be one higher. Which is baked into + // the constant to emphasise the all-divisible-by-8 property. - template - uint8_t palette_entry() { - return channel() | channel() | channel(); - } + static constexpr int h_half = h_total / 2; + static constexpr int hburst_start = 856; + static constexpr int hburst_end = 896; - // User-selected base address; constrained to a 64-byte boundary by the setter. - uint16_t screen_base_ = 0; + // Vertical timing parameters; all in terms of lines. As per the horizontal parameters above, + // lines begin with their first visible pixel (or the equivalent position). + static constexpr int v_active_gph = 256; + static constexpr int v_active_txt = 250; + static constexpr int v_disp_gph = v_active_gph - 1; + static constexpr int v_disp_txt = v_active_txt - 1; + static constexpr int vsync_start = 274; + static constexpr int vsync_end = 276; + static constexpr int v_rtc = 99; - // Parameters implied by mode selection. - uint16_t mode_base_ = 0; - bool mode_40_ = true; - bool mode_text_ = false; - enum class Bpp { - One = 1, Two = 2, Four = 4 - } mode_bpp_ = Bpp::One; + // Various signals that it was convenient to factor out. + int v_total() const { + return field_ ? 312 : 311; + } - // Frame position. - int v_count_ = 0; - int h_count_ = 0; - bool field_ = true; + bool last_line() const { + return char_row_ == (mode_text_ ? 9 : 7); + } - // Current working address. - uint16_t row_addr_ = 0; // Address, sans character row, adopted at the start of a row. - uint16_t byte_addr_ = 0; // Current working address, incremented as the raster moves across the line. - int char_row_ = 0; // Character row; 0–9 in text mode, 0–7 in graphics. + bool in_blank() const { + return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8; + } - // Sync states. - bool vsync_int_ = false; // True => vsync active. - bool hsync_int_ = false; // True => hsync active. - - // Horizontal timing parameters; all in terms of the 16Mhz pixel clock but conveniently all - // divisible by 8, so it's safe to count time with a 2Mhz input. - static constexpr int h_active = 640; - static constexpr int hsync_start = 768; - static constexpr int hsync_end = 832; - static constexpr int h_reset_addr = 1016; - static constexpr int h_total = 1024; // Minor digression from the FPGA original here; - // in this implementation the value is tested - // _after_ position increment rather than before/instead. - // So it needs to be one higher. Which is baked into - // the constant to emphasise the all-divisible-by-8 property. - - static constexpr int h_half = h_total / 2; - static constexpr int hburst_start = 856; - static constexpr int hburst_end = 896; - - // Vertical timing parameters; all in terms of lines. As per the horizontal parameters above, - // lines begin with their first visible pixel (or the equivalent position). - static constexpr int v_active_gph = 256; - static constexpr int v_active_txt = 250; - static constexpr int v_disp_gph = v_active_gph - 1; - static constexpr int v_disp_txt = v_active_txt - 1; - static constexpr int vsync_start = 274; - static constexpr int vsync_end = 276; - static constexpr int v_rtc = 99; - - // Various signals that it was convenient to factor out. - int v_total() const { - return field_ ? 312 : 311; - } - - bool last_line() const { - return char_row_ == (mode_text_ ? 9 : 7); - } - - bool in_blank() const { - return h_count_ >= h_active || (mode_text_ && v_count_ >= v_active_txt) || (!mode_text_ && v_count_ >= v_active_gph) || char_row_ >= 8; - } - - bool is_v_end() const { - return v_count_ == v_total(); - } + bool is_v_end() const { + return v_count_ == v_total(); + } }; } diff --git a/Machines/Amiga/Amiga.cpp b/Machines/Amiga/Amiga.cpp index f0fa4a2fe..806c0bd45 100644 --- a/Machines/Amiga/Amiga.cpp +++ b/Machines/Amiga/Amiga.cpp @@ -50,201 +50,201 @@ class ConcreteMachine: public MachineTypes::ScanProducer, public MachineTypes::TimedMachine, public Machine { - public: - ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - mc68000_(*this), - memory_(target.chip_ram, target.fast_ram), - chipset_(memory_, PALClockRate) - { - // Temporary: use a hard-coded Kickstart selection. - constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13; - ROM::Request request(rom_name); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data()); +public: + ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + mc68000_(*this), + memory_(target.chip_ram, target.fast_ram), + chipset_(memory_, PALClockRate) + { + // Temporary: use a hard-coded Kickstart selection. + constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13; + ROM::Request request(rom_name); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data()); - // For now, also hard-code assumption of PAL. - // (Assumption is both here and in the video timing of the Chipset). - set_clock_rate(PALClockRate); + // For now, also hard-code assumption of PAL. + // (Assumption is both here and in the video timing of the Chipset). + set_clock_rate(PALClockRate); - // Insert supplied media. - insert_media(target.media); + // Insert supplied media. + insert_media(target.media); + } + + // MARK: - MediaTarget. + + bool insert_media(const Analyser::Static::Media &media) final { + return chipset_.insert(media.disks); + } + + // MARK: - MC68000::BusHandler. + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + // Do a quick advance check for Chip RAM access; add a suitable delay if required. + HalfCycles total_length; + if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) { + total_length = chipset_.run_until_after_cpu_slot().duration; + assert(total_length >= cycle.length); + } else { + total_length = cycle.length; + chipset_.run_for(total_length); + } + mc68000_.set_interrupt_level(chipset_.get_interrupt_level()); + + // Check for assertion of reset. + if(cycle.operation & CPU::MC68000::Operation::Reset) { + memory_.reset(); + logger.info().append("Reset; PC is around %08x", mc68000_.get_state().registers.program_counter); } - // MARK: - MediaTarget. - - bool insert_media(const Analyser::Static::Media &media) final { - return chipset_.insert(media.disks); - } - - // MARK: - MC68000::BusHandler. - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - // Do a quick advance check for Chip RAM access; add a suitable delay if required. - HalfCycles total_length; - if(cycle.operation & CPU::MC68000::Operation::NewAddress && *cycle.address < 0x20'0000) { - total_length = chipset_.run_until_after_cpu_slot().duration; - assert(total_length >= cycle.length); - } else { - total_length = cycle.length; - chipset_.run_for(total_length); - } - mc68000_.set_interrupt_level(chipset_.get_interrupt_level()); - - // Check for assertion of reset. - if(cycle.operation & CPU::MC68000::Operation::Reset) { - memory_.reset(); - logger.info().append("Reset; PC is around %08x", mc68000_.get_state().registers.program_counter); - } - - // Autovector interrupts. - if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) { - mc68000_.set_is_peripheral_address(true); - return total_length - cycle.length; - } - - // Do nothing if no address is exposed. - if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length; - - // Grab the target address to pick a memory source. - const uint32_t address = cycle.host_endian_byte_address(); - - // Set VPA if this is [going to be] a CIA access. - mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000); - - if(!memory_.regions[address >> 18].read_write_mask) { - if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) { - // Check for various potential chip accesses. - - // Per the manual: - // - // CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte) - // CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte) - // - // but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think - // these might be listed the wrong way around. - // - // Additional assumption: the relevant CIA select lines are connected - // directly to the chip enables. - if((address & 0xe0'0000) == 0xa0'0000) { - const int reg = address >> 8; - const bool select_a = !(address & 0x1000); - const bool select_b = !(address & 0x2000); - - if(cycle.operation & CPU::MC68000::Operation::Read) { - uint16_t result = 0xffff; - if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0); - if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8); - cycle.set_value16(result); - } else { - if(select_a) chipset_.cia_a.write(reg, cycle.value8_low()); - if(select_b) chipset_.cia_b.write(reg, cycle.value8_high()); - } - -// logger.info().append("CIA %d %s %d of %04x", ((address >> 12) & 3)^3, operation & Microcycle::Read ? "read" : "write", reg & 0xf, cycle.value16()); - } else if(address >= 0xdf'f000 && address <= 0xdf'f1be) { - chipset_.perform(cycle); - } else if(address >= 0xe8'0000 && address < 0xe9'0000) { - // This is the Autoconf space; right now the only - // Autoconf device this emulator implements is fast RAM, - // which if present is provided as part of the memory map. - // - // Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx" - memory_.perform(cycle); - } else { - // This'll do for open bus, for now. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value16(0xffff); - } - - // Log only for the region that is definitely not just ROM this machine doesn't have. - if(address < 0xf0'0000) { - logger.error().append("Unmapped %s %06x of %04x", cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ", (*cycle.address)&0xffffff, cycle.value16()); - } - } - } - } else { - // A regular memory access. - cycle.apply( - &memory_.regions[address >> 18].contents[address], - memory_.regions[address >> 18].read_write_mask - ); - } - + // Autovector interrupts. + if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) { + mc68000_.set_is_peripheral_address(true); return total_length - cycle.length; } - private: - CPU::MC68000::Processor mc68000_; + // Do nothing if no address is exposed. + if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return total_length - cycle.length; - // MARK: - Memory map. + // Grab the target address to pick a memory source. + const uint32_t address = cycle.host_endian_byte_address(); - MemoryMap memory_; + // Set VPA if this is [going to be] a CIA access. + mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000); - // MARK: - Chipset. + if(!memory_.regions[address >> 18].read_write_mask) { + if((cycle.operation & (CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::SelectWord))) { + // Check for various potential chip accesses. - Chipset chipset_; + // Per the manual: + // + // CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte) + // CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte) + // + // but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think + // these might be listed the wrong way around. + // + // Additional assumption: the relevant CIA select lines are connected + // directly to the chip enables. + if((address & 0xe0'0000) == 0xa0'0000) { + const int reg = address >> 8; + const bool select_a = !(address & 0x1000); + const bool select_b = !(address & 0x2000); - // MARK: - Activity Source + if(cycle.operation & CPU::MC68000::Operation::Read) { + uint16_t result = 0xffff; + if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0); + if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8); + cycle.set_value16(result); + } else { + if(select_a) chipset_.cia_a.write(reg, cycle.value8_low()); + if(select_b) chipset_.cia_b.write(reg, cycle.value8_high()); + } - void set_activity_observer(Activity::Observer *observer) final { - chipset_.set_activity_observer(observer); +// logger.info().append("CIA %d %s %d of %04x", ((address >> 12) & 3)^3, operation & Microcycle::Read ? "read" : "write", reg & 0xf, cycle.value16()); + } else if(address >= 0xdf'f000 && address <= 0xdf'f1be) { + chipset_.perform(cycle); + } else if(address >= 0xe8'0000 && address < 0xe9'0000) { + // This is the Autoconf space; right now the only + // Autoconf device this emulator implements is fast RAM, + // which if present is provided as part of the memory map. + // + // Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx" + memory_.perform(cycle); + } else { + // This'll do for open bus, for now. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value16(0xffff); + } + + // Log only for the region that is definitely not just ROM this machine doesn't have. + if(address < 0xf0'0000) { + logger.error().append("Unmapped %s %06x of %04x", cycle.operation & CPU::MC68000::Operation::Read ? "read from " : "write to ", (*cycle.address)&0xffffff, cycle.value16()); + } + } + } + } else { + // A regular memory access. + cycle.apply( + &memory_.regions[address >> 18].contents[address], + memory_.regions[address >> 18].read_write_mask + ); } - // MARK: - MachineTypes::AudioProducer. + return total_length - cycle.length; + } - Outputs::Speaker::Speaker *get_speaker() final { - return chipset_.get_speaker(); - } +private: + CPU::MC68000::Processor mc68000_; - // MARK: - MachineTypes::ScanProducer. + // MARK: - Memory map. - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - chipset_.set_scan_target(scan_target); - } + MemoryMap memory_; - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return chipset_.get_scaled_scan_status(); - } + // MARK: - Chipset. - // MARK: - MachineTypes::TimedMachine. + Chipset chipset_; - void run_for(const Cycles cycles) final { - mc68000_.run_for(cycles); - } + // MARK: - Activity Source - void flush_output(int) final { - chipset_.flush(); - } + void set_activity_observer(Activity::Observer *observer) final { + chipset_.set_activity_observer(observer); + } - // MARK: - MachineTypes::MouseMachine. + // MARK: - MachineTypes::AudioProducer. - Inputs::Mouse &get_mouse() final { - return chipset_.get_mouse(); - } + Outputs::Speaker::Speaker *get_speaker() final { + return chipset_.get_speaker(); + } - // MARK: - MachineTypes::JoystickMachine. + // MARK: - MachineTypes::ScanProducer. - const std::vector> &get_joysticks() final { - return chipset_.get_joysticks(); - } + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + chipset_.set_scan_target(scan_target); + } - // MARK: - Keyboard. + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return chipset_.get_scaled_scan_status(); + } - Amiga::KeyboardMapper keyboard_mapper_; - KeyboardMapper *get_keyboard_mapper() { - return &keyboard_mapper_; - } + // MARK: - MachineTypes::TimedMachine. - void set_key_state(uint16_t key, bool is_pressed) { - chipset_.get_keyboard().set_key_state(key, is_pressed); - } + void run_for(const Cycles cycles) final { + mc68000_.run_for(cycles); + } - void clear_all_keys() { - chipset_.get_keyboard().clear_all_keys(); - } - }; + void flush_output(int) final { + chipset_.flush(); + } + + // MARK: - MachineTypes::MouseMachine. + + Inputs::Mouse &get_mouse() final { + return chipset_.get_mouse(); + } + + // MARK: - MachineTypes::JoystickMachine. + + const std::vector> &get_joysticks() final { + return chipset_.get_joysticks(); + } + + // MARK: - Keyboard. + + Amiga::KeyboardMapper keyboard_mapper_; + KeyboardMapper *get_keyboard_mapper() { + return &keyboard_mapper_; + } + + void set_key_state(uint16_t key, bool is_pressed) { + chipset_.get_keyboard().set_key_state(key, is_pressed); + } + + void clear_all_keys() { + chipset_.get_keyboard().clear_all_keys(); + } +}; } diff --git a/Machines/Amiga/Audio.hpp b/Machines/Amiga/Audio.hpp index 1f73faba8..17c4bd304 100644 --- a/Machines/Amiga/Audio.hpp +++ b/Machines/Amiga/Audio.hpp @@ -19,142 +19,142 @@ namespace Amiga { class Audio: public DMADevice<4> { - public: - Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate); +public: + Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate); - /// Idiomatic call-in for DMA scheduling; indicates that this class may - /// perform a DMA access for the stated channel now. - bool advance_dma(int channel); + /// Idiomatic call-in for DMA scheduling; indicates that this class may + /// perform a DMA access for the stated channel now. + bool advance_dma(int channel); - /// Advances output by one DMA window, which is implicitly two cycles - /// at the output rate that was specified to the constructor. - void output(); + /// Advances output by one DMA window, which is implicitly two cycles + /// at the output rate that was specified to the constructor. + void output(); - /// Sets the total number of words to fetch for the given channel. - void set_length(int channel, uint16_t); + /// Sets the total number of words to fetch for the given channel. + void set_length(int channel, uint16_t); - /// Sets the number of DMA windows between each 8-bit output, - /// in the same time base as @c ticks_per_line. - void set_period(int channel, uint16_t); + /// Sets the number of DMA windows between each 8-bit output, + /// in the same time base as @c ticks_per_line. + void set_period(int channel, uint16_t); - /// Sets the output volume for the given channel; if bit 6 is set - /// then output is maximal; otherwise bits 0–5 select - /// a volume of [0–63]/64, on a logarithmic scale. - void set_volume(int channel, uint16_t); + /// Sets the output volume for the given channel; if bit 6 is set + /// then output is maximal; otherwise bits 0–5 select + /// a volume of [0–63]/64, on a logarithmic scale. + void set_volume(int channel, uint16_t); - /// Sets the next two samples of audio to output. - template void set_data(int channel, uint16_t); + /// Sets the next two samples of audio to output. + template void set_data(int channel, uint16_t); - /// Provides a copy of the DMA enable flags, for the purpose of - /// determining which channels are enabled for DMA. - void set_channel_enables(uint16_t); + /// Provides a copy of the DMA enable flags, for the purpose of + /// determining which channels are enabled for DMA. + void set_channel_enables(uint16_t); - /// Sets which channels, if any, modulate period or volume of - /// their neighbours. - void set_modulation_flags(uint16_t); + /// Sets which channels, if any, modulate period or volume of + /// their neighbours. + void set_modulation_flags(uint16_t); - /// Sets which interrupt requests are currently active. - void set_interrupt_requests(uint16_t); + /// Sets which interrupt requests are currently active. + void set_interrupt_requests(uint16_t); - /// Obtains the output source. - Outputs::Speaker::Speaker *get_speaker() { - return &speaker_; + /// Obtains the output source. + Outputs::Speaker::Speaker *get_speaker() { + return &speaker_; + } + +private: + struct Channel { + // The data latch plus a count of unused samples + // in the latch, which will always be 0, 1 or 2. + uint16_t data = 0x0000; + bool wants_data = false; + uint16_t data_latch = 0x0000; + + // The DMA address; unlike most of the Amiga Chipset, + // the user posts a value to feed a pointer, rather + // than having access to the pointer itself. + bool should_reload_address = false; + uint32_t data_address = 0x0000'0000; + + // Number of words remaining in DMA data. + uint16_t length = 0; + uint16_t length_counter = 0; + + // Number of ticks between each sample, plus the + // current counter, which counts downward. + uint16_t period = 0; + uint16_t period_counter = 0; + + // Modulation / attach flags. + bool attach_period = false; + bool attach_volume = false; + + // Output volume, [0, 64]. + uint8_t volume = 0; + uint8_t volume_latch = 0; + + // Indicates whether DMA is enabled for this channel. + bool dma_enabled = false; + + // Records whether this audio interrupt is pending. + bool interrupt_pending = false; + bool will_request_interrupt = false; + + // Replicates the Hardware Reference Manual state machine; + // comments indicate which of the documented states each + // label refers to. + enum class State { + Disabled, // 000 + WaitingForDummyDMA, // 001 + WaitingForDMA, // 101 + PlayingHigh, // 010 + PlayingLow, // 011 + } state = State::Disabled; + + /// Dispatches to the appropriate templatised output for the current state. + /// @param moduland The channel to modulate, if modulation is enabled. + /// @returns @c true if an interrupt should be posted; @c false otherwise. + bool output(Channel *moduland); + + /// Applies dynamic logic for @c state, mostly testing for potential state transitions. + /// @param moduland The channel to modulate, if modulation is enabled. + /// @returns @c true if an interrupt should be posted; @c false otherwise. + template bool output(Channel *moduland); + + /// Transitions from @c begin to @c end, calling the appropriate @c begin_state + /// and taking any steps specific to that particular transition. + /// @param moduland The channel to modulate, if modulation is enabled. + /// @returns @c true if an interrupt should be posted; @c false otherwise. + template bool transit(Channel *moduland); + + /// Begins @c state, performing all fixed logic that would otherwise have to be + /// repeated endlessly in the relevant @c output. + /// @param moduland The channel to modulate, if modulation is enabled. + template void begin_state(Channel *moduland); + + /// Provides the common length-decrementing logic used when transitioning + /// between PlayingHigh and PlayingLow in either direction. + void decrement_length(); + + // Output state. + int8_t output_level = 0; + uint8_t output_phase = 0; + bool output_enabled = false; + + void reset_output_phase() { + output_phase = 0; + output_enabled = (volume_latch > 0) && !attach_period && !attach_volume; } + } channels_[4]; - private: - struct Channel { - // The data latch plus a count of unused samples - // in the latch, which will always be 0, 1 or 2. - uint16_t data = 0x0000; - bool wants_data = false; - uint16_t data_latch = 0x0000; + // Transient output state, and its destination. + Outputs::Speaker::PushLowpass speaker_; + Concurrency::AsyncTaskQueue queue_; - // The DMA address; unlike most of the Amiga Chipset, - // the user posts a value to feed a pointer, rather - // than having access to the pointer itself. - bool should_reload_address = false; - uint32_t data_address = 0x0000'0000; - - // Number of words remaining in DMA data. - uint16_t length = 0; - uint16_t length_counter = 0; - - // Number of ticks between each sample, plus the - // current counter, which counts downward. - uint16_t period = 0; - uint16_t period_counter = 0; - - // Modulation / attach flags. - bool attach_period = false; - bool attach_volume = false; - - // Output volume, [0, 64]. - uint8_t volume = 0; - uint8_t volume_latch = 0; - - // Indicates whether DMA is enabled for this channel. - bool dma_enabled = false; - - // Records whether this audio interrupt is pending. - bool interrupt_pending = false; - bool will_request_interrupt = false; - - // Replicates the Hardware Reference Manual state machine; - // comments indicate which of the documented states each - // label refers to. - enum class State { - Disabled, // 000 - WaitingForDummyDMA, // 001 - WaitingForDMA, // 101 - PlayingHigh, // 010 - PlayingLow, // 011 - } state = State::Disabled; - - /// Dispatches to the appropriate templatised output for the current state. - /// @param moduland The channel to modulate, if modulation is enabled. - /// @returns @c true if an interrupt should be posted; @c false otherwise. - bool output(Channel *moduland); - - /// Applies dynamic logic for @c state, mostly testing for potential state transitions. - /// @param moduland The channel to modulate, if modulation is enabled. - /// @returns @c true if an interrupt should be posted; @c false otherwise. - template bool output(Channel *moduland); - - /// Transitions from @c begin to @c end, calling the appropriate @c begin_state - /// and taking any steps specific to that particular transition. - /// @param moduland The channel to modulate, if modulation is enabled. - /// @returns @c true if an interrupt should be posted; @c false otherwise. - template bool transit(Channel *moduland); - - /// Begins @c state, performing all fixed logic that would otherwise have to be - /// repeated endlessly in the relevant @c output. - /// @param moduland The channel to modulate, if modulation is enabled. - template void begin_state(Channel *moduland); - - /// Provides the common length-decrementing logic used when transitioning - /// between PlayingHigh and PlayingLow in either direction. - void decrement_length(); - - // Output state. - int8_t output_level = 0; - uint8_t output_phase = 0; - bool output_enabled = false; - - void reset_output_phase() { - output_phase = 0; - output_enabled = (volume_latch > 0) && !attach_period && !attach_volume; - } - } channels_[4]; - - // Transient output state, and its destination. - Outputs::Speaker::PushLowpass speaker_; - Concurrency::AsyncTaskQueue queue_; - - using AudioBuffer = std::array; - static constexpr int BufferCount = 3; - AudioBuffer buffer_[BufferCount]; - std::atomic buffer_available_[BufferCount]; - size_t buffer_pointer_ = 0, sample_pointer_ = 0; + using AudioBuffer = std::array; + static constexpr int BufferCount = 3; + AudioBuffer buffer_[BufferCount]; + std::atomic buffer_available_[BufferCount]; + size_t buffer_pointer_ = 0, sample_pointer_ = 0; }; } diff --git a/Machines/Amiga/Bitplanes.hpp b/Machines/Amiga/Bitplanes.hpp index 6c6fad3dc..2a739a386 100644 --- a/Machines/Amiga/Bitplanes.hpp +++ b/Machines/Amiga/Bitplanes.hpp @@ -31,18 +31,18 @@ struct BitplaneData: public std::array { }; class Bitplanes: public DMADevice<6, 2> { - public: - using DMADevice::DMADevice; +public: + using DMADevice::DMADevice; - bool advance_dma(int cycle); - void do_end_of_line(); - void set_control(uint16_t); + bool advance_dma(int cycle); + void do_end_of_line(); + void set_control(uint16_t); - private: - bool is_high_res_ = false; - int plane_count_ = 0; +private: + bool is_high_res_ = false; + int plane_count_ = 0; - BitplaneData next; + BitplaneData next; }; template constexpr SourceT bitplane_swizzle(SourceT value) { @@ -55,44 +55,44 @@ template constexpr SourceT bitplane_swizzle(SourceT value) { } class BitplaneShifter { - public: - /// Installs a new set of output pixels. - void set( - const BitplaneData &previous, - const BitplaneData &next, - int odd_delay, - int even_delay); +public: + /// Installs a new set of output pixels. + void set( + const BitplaneData &previous, + const BitplaneData &next, + int odd_delay, + int even_delay); - /// Shifts either two pixels (in low-res mode) or four pixels (in high-res). - void shift(bool high_res) { - constexpr int shifts[] = {16, 32}; + /// Shifts either two pixels (in low-res mode) or four pixels (in high-res). + void shift(bool high_res) { + constexpr int shifts[] = {16, 32}; - data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res])); - data_[0] <<= shifts[high_res]; + data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res])); + data_[0] <<= shifts[high_res]; + } + + /// @returns The next four pixels to output; in low-resolution mode only two + /// of them will be unique. + /// + /// The value is arranges so that MSB = first pixel to output, LSB = last. + /// + /// Each byte is swizzled to provide easier playfield separation, being in the form: + /// b6, b7 = 0; + /// b3–b5: planes 1, 3 and 5; + /// b0–b2: planes 0, 2 and 4. + uint32_t get(bool high_res) { + if(high_res) { + return uint32_t(data_[1] >> 32); + } else { + uint32_t result = uint16_t(data_[1] >> 48); + result = ((result & 0xff00) << 8) | (result & 0x00ff); + result |= result << 8; + return result; } + } - /// @returns The next four pixels to output; in low-resolution mode only two - /// of them will be unique. - /// - /// The value is arranges so that MSB = first pixel to output, LSB = last. - /// - /// Each byte is swizzled to provide easier playfield separation, being in the form: - /// b6, b7 = 0; - /// b3–b5: planes 1, 3 and 5; - /// b0–b2: planes 0, 2 and 4. - uint32_t get(bool high_res) { - if(high_res) { - return uint32_t(data_[1] >> 32); - } else { - uint32_t result = uint16_t(data_[1] >> 48); - result = ((result & 0xff00) << 8) | (result & 0x00ff); - result |= result << 8; - return result; - } - } - - private: - std::array data_{}; +private: + std::array data_{}; }; diff --git a/Machines/Amiga/Blitter.hpp b/Machines/Amiga/Blitter.hpp index 232428385..3cf28afda 100644 --- a/Machines/Amiga/Blitter.hpp +++ b/Machines/Amiga/Blitter.hpp @@ -24,104 +24,104 @@ namespace Amiga { and can subsequently be retrieved. This is included for testing purposes. */ template class Blitter: public DMADevice<4, 4> { - public: - using DMADevice::DMADevice; +public: + using DMADevice::DMADevice; - template void set_pointer(uint16_t value) { - DMADevice<4, 4>::set_pointer(value); - } + template void set_pointer(uint16_t value) { + DMADevice<4, 4>::set_pointer(value); + } - // Various setters; it's assumed that address decoding is handled externally. - // - // In all cases where a channel is identified numerically, it's taken that - // 0 = A, 1 = B, 2 = C, 3 = D. - void set_control(int index, uint16_t value); - void set_first_word_mask(uint16_t value); - void set_last_word_mask(uint16_t value); + // Various setters; it's assumed that address decoding is handled externally. + // + // In all cases where a channel is identified numerically, it's taken that + // 0 = A, 1 = B, 2 = C, 3 = D. + void set_control(int index, uint16_t value); + void set_first_word_mask(uint16_t value); + void set_last_word_mask(uint16_t value); - void set_size(uint16_t value); - void set_minterms(uint16_t value); -// void set_vertical_size(uint16_t value); -// void set_horizontal_size(uint16_t value); - void set_data(int channel, uint16_t value); + void set_size(uint16_t value); + void set_minterms(uint16_t value); +// void set_vertical_size(uint16_t value); +// void set_horizontal_size(uint16_t value); + void set_data(int channel, uint16_t value); - uint16_t get_status(); + uint16_t get_status(); - template bool advance_dma(); + template bool advance_dma(); - struct Transaction { - enum class Type { - SkippedSlot, - ReadA, - ReadB, - ReadC, - AddToPipeline, - WriteFromPipeline - } type = Type::SkippedSlot; + struct Transaction { + enum class Type { + SkippedSlot, + ReadA, + ReadB, + ReadC, + AddToPipeline, + WriteFromPipeline + } type = Type::SkippedSlot; - uint32_t address = 0; - uint16_t value = 0; + uint32_t address = 0; + uint16_t value = 0; - Transaction() = default; - Transaction(Type type) : type(type) {} - Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {} + Transaction() = default; + Transaction(Type type) : type(type) {} + Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {} - std::string to_string() const { - std::string result; + std::string to_string() const { + std::string result; - switch(type) { - case Type::SkippedSlot: result = "SkippedSlot"; break; - case Type::ReadA: result = "ReadA"; break; - case Type::ReadB: result = "ReadB"; break; - case Type::ReadC: result = "ReadC"; break; - case Type::AddToPipeline: result = "AddToPipeline"; break; - case Type::WriteFromPipeline: result = "WriteFromPipeline"; break; - } - - result += " address:" + std::to_string(address) + " value:" + std::to_string(value); - return result; + switch(type) { + case Type::SkippedSlot: result = "SkippedSlot"; break; + case Type::ReadA: result = "ReadA"; break; + case Type::ReadB: result = "ReadB"; break; + case Type::ReadC: result = "ReadC"; break; + case Type::AddToPipeline: result = "AddToPipeline"; break; + case Type::WriteFromPipeline: result = "WriteFromPipeline"; break; } - }; - std::vector get_and_reset_transactions(); - private: - int width_ = 0, height_ = 0; - int shifts_[2]{}; - uint16_t a_mask_[2] = {0xffff, 0xffff}; + result += " address:" + std::to_string(address) + " value:" + std::to_string(value); + return result; + } + }; + std::vector get_and_reset_transactions(); - bool line_mode_ = false; - bool one_dot_ = false; - int line_direction_ = 0; - int line_sign_ = 1; +private: + int width_ = 0, height_ = 0; + int shifts_[2]{}; + uint16_t a_mask_[2] = {0xffff, 0xffff}; - uint32_t direction_ = 1; - bool inclusive_fill_ = false; - bool exclusive_fill_ = false; - bool fill_carry_ = false; + bool line_mode_ = false; + bool one_dot_ = false; + int line_direction_ = 0; + int line_sign_ = 1; - uint8_t minterms_ = 0; - uint32_t a32_ = 0, b32_ = 0; - uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0; + uint32_t direction_ = 1; + bool inclusive_fill_ = false; + bool exclusive_fill_ = false; + bool fill_carry_ = false; - bool not_zero_flag_ = false; + uint8_t minterms_ = 0; + uint32_t a32_ = 0, b32_ = 0; + uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0; - BlitterSequencer sequencer_; - uint32_t write_address_ = 0xffff'ffff; - uint16_t write_value_ = 0; - enum WritePhase { - Starting, Full - } write_phase_; - int y_, x_; - uint16_t transient_a_mask_; - bool busy_ = false; - int loop_index_ = -1; + bool not_zero_flag_ = false; - int error_ = 0; - bool draw_ = false; - bool has_c_data_ = false; + BlitterSequencer sequencer_; + uint32_t write_address_ = 0xffff'ffff; + uint16_t write_value_ = 0; + enum WritePhase { + Starting, Full + } write_phase_; + int y_, x_; + uint16_t transient_a_mask_; + bool busy_ = false; + int loop_index_ = -1; - void add_modulos(); - std::vector transactions_; + int error_ = 0; + bool draw_ = false; + bool has_c_data_ = false; + + void add_modulos(); + std::vector transactions_; }; } diff --git a/Machines/Amiga/BlitterSequencer.hpp b/Machines/Amiga/BlitterSequencer.hpp index e6acb0279..f3b46483c 100644 --- a/Machines/Amiga/BlitterSequencer.hpp +++ b/Machines/Amiga/BlitterSequencer.hpp @@ -19,146 +19,146 @@ namespace Amiga { relying on tables. */ class BlitterSequencer { - public: - enum class Channel { - /// Tells the caller to calculate and load a new piece of output - /// into the output pipeline. - /// - /// If any inputs are enabled then a one-slot output pipeline applies: - /// output will rest in the pipeline for one write phase before being written. - Write, - /// Indicates that a write should occur if anything is in the pipeline, otherwise - /// no activity should occur. - FlushPipeline, - /// The caller should read from channel C. - C, - /// The caller should read from channel B. - B, - /// The caller should read from channel A. - A, - /// Indicates an unused DMA slot. - None - }; +public: + enum class Channel { + /// Tells the caller to calculate and load a new piece of output + /// into the output pipeline. + /// + /// If any inputs are enabled then a one-slot output pipeline applies: + /// output will rest in the pipeline for one write phase before being written. + Write, + /// Indicates that a write should occur if anything is in the pipeline, otherwise + /// no activity should occur. + FlushPipeline, + /// The caller should read from channel C. + C, + /// The caller should read from channel B. + B, + /// The caller should read from channel A. + A, + /// Indicates an unused DMA slot. + None + }; - /// Sets the current control value, which indicates which - /// channels are enabled. - void set_control(int control) { - control_ = control & 0xf; - index_ = 0; // TODO: this probably isn't accurate; case caught is a change - // of control values during a blit. + /// Sets the current control value, which indicates which + /// channels are enabled. + void set_control(int control) { + control_ = control & 0xf; + index_ = 0; // TODO: this probably isn't accurate; case caught is a change + // of control values during a blit. + } + + /// Indicates that blitting should conclude after this step, i.e. + /// whatever is being fetched now is part of the final set of input data; + /// this is safe to call following a fetch request on any channel. + void complete() { + next_phase_ = + (control_ == 0x9 || control_ == 0xb || control_ == 0xd) ? + Phase::PauseAndComplete : Phase::Complete; + } + + /// Begins a blit operation. + void begin() { + phase_ = next_phase_ = Phase::Ongoing; + index_ = loop_ = 0; + } + + /// Provides the next channel to fetch from, or that a write is required, + /// along with a count of complete channel iterations so far completed. + std::pair next() { + switch(phase_) { + default: break; + + case Phase::Complete: + return std::make_pair(Channel::FlushPipeline, loop_); + + case Phase::PauseAndComplete: + phase_ = Phase::Complete; + return std::make_pair(Channel::None, loop_); } - /// Indicates that blitting should conclude after this step, i.e. - /// whatever is being fetched now is part of the final set of input data; - /// this is safe to call following a fetch request on any channel. - void complete() { - next_phase_ = - (control_ == 0x9 || control_ == 0xb || control_ == 0xd) ? - Phase::PauseAndComplete : Phase::Complete; + Channel next = Channel::None; + + switch(control_) { + default: break; + + case 0: next = next_channel(pattern0); break; + case 1: next = next_channel(pattern1); break; + case 2: next = next_channel(pattern2); break; + case 3: next = next_channel(pattern3); break; + case 4: next = next_channel(pattern4); break; + case 5: next = next_channel(pattern5); break; + case 6: next = next_channel(pattern6); break; + case 7: next = next_channel(pattern7); break; + case 8: next = next_channel(pattern8); break; + case 9: next = next_channel(pattern9); break; + case 10: next = next_channel(patternA); break; + case 11: next = next_channel(patternB); break; + case 12: next = next_channel(patternC); break; + case 13: next = next_channel(patternD); break; + case 14: next = next_channel(patternE); break; + case 15: next = next_channel(patternF); break; } - /// Begins a blit operation. - void begin() { - phase_ = next_phase_ = Phase::Ongoing; - index_ = loop_ = 0; + return std::make_pair(next, loop_); + } + + template bool channel_enabled() { + return control_ & (8 >> channel); + } + +private: + static constexpr std::array pattern0 = { Channel::None }; + static constexpr std::array pattern1 = { Channel::Write, Channel::None }; + static constexpr std::array pattern2 = { Channel::C, Channel::None }; + static constexpr std::array pattern3 = { Channel::C, Channel::Write, Channel::None }; + static constexpr std::array pattern4 = { Channel::B, Channel::None, Channel::None }; + static constexpr std::array pattern5 = { Channel::B, Channel::Write, Channel::None }; + static constexpr std::array pattern6 = { Channel::B, Channel::C, Channel::None }; + static constexpr std::array pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None }; + static constexpr std::array pattern8 = { Channel::A, Channel::None }; + static constexpr std::array pattern9 = { Channel::A, Channel::Write }; + static constexpr std::array patternA = { Channel::A, Channel::C }; + static constexpr std::array patternB = { Channel::A, Channel::C, Channel::Write }; + static constexpr std::array patternC = { Channel::A, Channel::B, Channel::None }; + static constexpr std::array patternD = { Channel::A, Channel::B, Channel::Write }; + static constexpr std::array patternE = { Channel::A, Channel::B, Channel::C }; + static constexpr std::array patternF = { Channel::A, Channel::B, Channel::C, Channel::Write }; + template Channel next_channel(const ArrayT &list) { + loop_ += index_ / list.size(); + index_ %= list.size(); + const Channel result = list[index_]; + ++index_; + if(index_ == list.size()) { + phase_ = next_phase_; } + return result; + } - /// Provides the next channel to fetch from, or that a write is required, - /// along with a count of complete channel iterations so far completed. - std::pair next() { - switch(phase_) { - default: break; + // Current control flags, i.e. which channels are enabled. + int control_ = 0; - case Phase::Complete: - return std::make_pair(Channel::FlushPipeline, loop_); + // Index into the pattern table for this blit. + size_t index_ = 0; - case Phase::PauseAndComplete: - phase_ = Phase::Complete; - return std::make_pair(Channel::None, loop_); - } + // Number of times the entire pattern table has been completed. + int loop_ = 0; - Channel next = Channel::None; + enum class Phase { + /// Return the next thing in the pattern table and advance. + /// If looping from the end of the pattern table to the start, + /// set phase_ to next_phase_. + Ongoing, + /// Return a Channel::None and advancce to phase_ = Phase::Complete. + PauseAndComplete, + /// Return Channel::Write indefinitely. + Complete + }; - switch(control_) { - default: break; - - case 0: next = next_channel(pattern0); break; - case 1: next = next_channel(pattern1); break; - case 2: next = next_channel(pattern2); break; - case 3: next = next_channel(pattern3); break; - case 4: next = next_channel(pattern4); break; - case 5: next = next_channel(pattern5); break; - case 6: next = next_channel(pattern6); break; - case 7: next = next_channel(pattern7); break; - case 8: next = next_channel(pattern8); break; - case 9: next = next_channel(pattern9); break; - case 10: next = next_channel(patternA); break; - case 11: next = next_channel(patternB); break; - case 12: next = next_channel(patternC); break; - case 13: next = next_channel(patternD); break; - case 14: next = next_channel(patternE); break; - case 15: next = next_channel(patternF); break; - } - - return std::make_pair(next, loop_); - } - - template bool channel_enabled() { - return control_ & (8 >> channel); - } - - private: - static constexpr std::array pattern0 = { Channel::None }; - static constexpr std::array pattern1 = { Channel::Write, Channel::None }; - static constexpr std::array pattern2 = { Channel::C, Channel::None }; - static constexpr std::array pattern3 = { Channel::C, Channel::Write, Channel::None }; - static constexpr std::array pattern4 = { Channel::B, Channel::None, Channel::None }; - static constexpr std::array pattern5 = { Channel::B, Channel::Write, Channel::None }; - static constexpr std::array pattern6 = { Channel::B, Channel::C, Channel::None }; - static constexpr std::array pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None }; - static constexpr std::array pattern8 = { Channel::A, Channel::None }; - static constexpr std::array pattern9 = { Channel::A, Channel::Write }; - static constexpr std::array patternA = { Channel::A, Channel::C }; - static constexpr std::array patternB = { Channel::A, Channel::C, Channel::Write }; - static constexpr std::array patternC = { Channel::A, Channel::B, Channel::None }; - static constexpr std::array patternD = { Channel::A, Channel::B, Channel::Write }; - static constexpr std::array patternE = { Channel::A, Channel::B, Channel::C }; - static constexpr std::array patternF = { Channel::A, Channel::B, Channel::C, Channel::Write }; - template Channel next_channel(const ArrayT &list) { - loop_ += index_ / list.size(); - index_ %= list.size(); - const Channel result = list[index_]; - ++index_; - if(index_ == list.size()) { - phase_ = next_phase_; - } - return result; - } - - // Current control flags, i.e. which channels are enabled. - int control_ = 0; - - // Index into the pattern table for this blit. - size_t index_ = 0; - - // Number of times the entire pattern table has been completed. - int loop_ = 0; - - enum class Phase { - /// Return the next thing in the pattern table and advance. - /// If looping from the end of the pattern table to the start, - /// set phase_ to next_phase_. - Ongoing, - /// Return a Channel::None and advancce to phase_ = Phase::Complete. - PauseAndComplete, - /// Return Channel::Write indefinitely. - Complete - }; - - // Current sequencer pahse. - Phase phase_ = Phase::Complete; - // Phase to assume at the end of this iteration of the sequence table. - Phase next_phase_ = Phase::Complete; + // Current sequencer pahse. + Phase phase_ = Phase::Complete; + // Phase to assume at the end of this iteration of the sequence table. + Phase next_phase_ = Phase::Complete; }; } diff --git a/Machines/Amiga/Chipset.hpp b/Machines/Amiga/Chipset.hpp index 6bc59f939..cdc16e968 100644 --- a/Machines/Amiga/Chipset.hpp +++ b/Machines/Amiga/Chipset.hpp @@ -37,341 +37,340 @@ namespace Amiga { class Chipset: private ClockingHint::Observer { +public: + Chipset(MemoryMap &memory_map, int input_clock_rate); + + struct Changes { + int interrupt_level = 0; + HalfCycles duration; + + Changes &operator += (const Changes &rhs) { + duration += rhs.duration; + return *this; + } + }; + + /// Advances the stated amount of time. + Changes run_for(HalfCycles); + + /// Advances to the end of the next available CPU slot. + Changes run_until_after_cpu_slot(); + + /// Performs the provided microcycle, which the caller guarantees to be a memory access. + template + void perform(const Microcycle &cycle) { + const uint32_t register_address = *cycle.address & ChipsetAddressMask; + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value16(read(register_address)); + } else { + write(register_address, cycle.value16()); + } + } + + /// Sets the current state of the CIA interrupt lines. + void set_cia_interrupts(bool cia_a, bool cia_b); + + /// Provides the chipset's current interrupt level. + int get_interrupt_level() { + return interrupt_level_; + } + + /// Inserts the disks provided. + /// @returns @c true if anything was inserted; @c false otherwise. + bool insert(const std::vector> &disks); + + // The standard CRT set. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + Outputs::Display::ScanStatus get_scaled_scan_status() const; + void set_display_type(Outputs::Display::DisplayType); + Outputs::Display::DisplayType get_display_type() const; + + // Activity observation. + void set_activity_observer(Activity::Observer *observer) { + cia_a_handler_.set_activity_observer(observer); + disk_controller_.set_activity_observer(observer); + } + + // Keyboard and mouse exposure. + Keyboard &get_keyboard() { + return keyboard_; + } + + const std::vector> &get_joysticks() { + return joysticks_; + } + + // Synchronisation. + void flush(); + + // Input for receiving collected bitplanes. + void post_bitplanes(const BitplaneData &data); + + // Obtains the source of audio output. + Outputs::Speaker::Speaker *get_speaker() { + return audio_.get_speaker(); + } + +private: + friend class DMADeviceBase; + + // MARK: - Register read/write functions. + uint16_t read(uint32_t address, bool allow_conversion = true); + void write(uint32_t address, uint16_t value, bool allow_conversion = true); + static constexpr uint32_t ChipsetAddressMask = 0x1fe; + friend class Copper; + + // MARK: - E Clock and keyboard dividers. + + HalfCycles cia_divider_; + HalfCycles keyboard_divider_; + + // MARK: - Interrupts. + + uint16_t interrupt_enable_ = 0; + uint16_t interrupt_requests_ = 0; + int interrupt_level_ = 0; + + void update_interrupts(); + void posit_interrupt(InterruptFlag::FlagT); + + // MARK: - Scheduler. + + template Changes run(HalfCycles duration = HalfCycles::max()); + template int advance_slots(int, int); + template bool perform_cycle(); + template void output(); + void output_pixels(int cycles_until_sync); + void apply_ham(uint8_t); + + // MARK: - DMA Control, Scheduler and Blitter. + + uint16_t dma_control_ = 0; + Blitter blitter_; + + // MARK: - Sprites and collision flags. + + std::array sprites_; + std::array sprite_shifters_; + uint16_t collisions_ = 0, collisions_flags_= 0; + + uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0; + + // MARK: - Raster position and state. + + // Definitions related to PAL/NTSC. + // (Default values are PAL). + int line_length_ = 227; + int short_field_height_ = 312; + int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20 + + // Current raster position. + int line_cycle_ = 0, y_ = 0; + + // Parameters affecting bitplane collection and output. + uint16_t display_window_start_[2] = {0, 0}; + uint16_t display_window_stop_[2] = {0, 0}; + uint16_t fetch_window_[2] = {0, 0}; + + // Ephemeral bitplane collection state. + bool fetch_vertical_ = false; + bool display_horizontal_ = false; + bool did_fetch_ = false; + + int horizontal_offset_ = 0; + enum HorizontalFetch { + Started, WillRequestStop, StopRequested, Stopped + } horizontal_fetch_ = HorizontalFetch::Stopped; + + // Output state. + uint16_t border_colour_ = 0; + bool is_border_ = true; + int zone_duration_ = 0; + uint16_t *pixels_ = nullptr; + uint16_t last_colour_ = 0; // Retained for HAM mode. + void flush_output(); + + Bitplanes bitplanes_; + + BitplaneData next_bitplanes_, previous_bitplanes_; + bool has_next_bitplanes_ = false; + + int odd_priority_ = 0, even_priority_ = 0; + bool even_over_odd_ = false; + bool hold_and_modify_ = false; + bool dual_playfields_ = false; + bool interlace_ = false; + bool is_long_field_ = false; + + BitplaneShifter bitplane_pixels_; + + int odd_delay_ = 0, even_delay_ = 0; + bool is_high_res_ = false; + + // MARK: - Copper. + + Copper copper_; + + // MARK: - Audio. + + Audio audio_; + + // MARK: - Serial port. + + class SerialPort { public: - Chipset(MemoryMap &memory_map, int input_clock_rate); - - struct Changes { - int interrupt_level = 0; - HalfCycles duration; - - Changes &operator += (const Changes &rhs) { - duration += rhs.duration; - return *this; - } - }; - - /// Advances the stated amount of time. - Changes run_for(HalfCycles); - - /// Advances to the end of the next available CPU slot. - Changes run_until_after_cpu_slot(); - - /// Performs the provided microcycle, which the caller guarantees to be a memory access. - template - void perform(const Microcycle &cycle) { - const uint32_t register_address = *cycle.address & ChipsetAddressMask; - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value16(read(register_address)); - } else { - write(register_address, cycle.value16()); - } - } - - /// Sets the current state of the CIA interrupt lines. - void set_cia_interrupts(bool cia_a, bool cia_b); - - /// Provides the chipset's current interrupt level. - int get_interrupt_level() { - return interrupt_level_; - } - - /// Inserts the disks provided. - /// @returns @c true if anything was inserted; @c false otherwise. - bool insert(const std::vector> &disks); - - // The standard CRT set. - void set_scan_target(Outputs::Display::ScanTarget *scan_target); - Outputs::Display::ScanStatus get_scaled_scan_status() const; - void set_display_type(Outputs::Display::DisplayType); - Outputs::Display::DisplayType get_display_type() const; - - // Activity observation. - void set_activity_observer(Activity::Observer *observer) { - cia_a_handler_.set_activity_observer(observer); - disk_controller_.set_activity_observer(observer); - } - - // Keyboard and mouse exposure. - Keyboard &get_keyboard() { - return keyboard_; - } - - const std::vector> &get_joysticks() { - return joysticks_; - } - - // Synchronisation. - void flush(); - - // Input for receiving collected bitplanes. - void post_bitplanes(const BitplaneData &data); - - // Obtains the source of audio output. - Outputs::Speaker::Speaker *get_speaker() { - return audio_.get_speaker(); - } + void set_control(uint16_t); + void set_data(uint16_t); + uint16_t get_status(); private: - friend class DMADeviceBase; +// uint16_t value = 0, reload = 0; +// uint16_t shift = 0, receive_shift = 0; +// uint16_t status; + } serial_; - // MARK: - Register read/write functions. - uint16_t read(uint32_t address, bool allow_conversion = true); - void write(uint32_t address, uint16_t value, bool allow_conversion = true); - static constexpr uint32_t ChipsetAddressMask = 0x1fe; - friend class Copper; + // MARK: - Pixel output. - // MARK: - E Clock and keyboard dividers. + Outputs::CRT::CRT crt_; + uint16_t palette_[32]{}; + uint16_t swizzled_palette_[64]{}; - HalfCycles cia_divider_; - HalfCycles keyboard_divider_; + // MARK: - Mouse. +private: + Mouse mouse_; - // MARK: - Interrupts. +public: + Inputs::Mouse &get_mouse() { + return mouse_; + } - uint16_t interrupt_enable_ = 0; - uint16_t interrupt_requests_ = 0; - int interrupt_level_ = 0; + // MARK: - Joystick. +private: + std::vector> joysticks_; + Joystick &joystick(size_t index) const { + return *static_cast(joysticks_[index].get()); + } - void update_interrupts(); - void posit_interrupt(InterruptFlag::FlagT); - - // MARK: - Scheduler. - - template Changes run(HalfCycles duration = HalfCycles::max()); - template int advance_slots(int, int); - template bool perform_cycle(); - template void output(); - void output_pixels(int cycles_until_sync); - void apply_ham(uint8_t); - - // MARK: - DMA Control, Scheduler and Blitter. - - uint16_t dma_control_ = 0; - Blitter blitter_; - - // MARK: - Sprites and collision flags. - - std::array sprites_; - std::array sprite_shifters_; - uint16_t collisions_ = 0, collisions_flags_= 0; - - uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0; - - // MARK: - Raster position and state. - - // Definitions related to PAL/NTSC. - // (Default values are PAL). - int line_length_ = 227; - int short_field_height_ = 312; - int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20 - - // Current raster position. - int line_cycle_ = 0, y_ = 0; - - // Parameters affecting bitplane collection and output. - uint16_t display_window_start_[2] = {0, 0}; - uint16_t display_window_stop_[2] = {0, 0}; - uint16_t fetch_window_[2] = {0, 0}; - - // Ephemeral bitplane collection state. - bool fetch_vertical_ = false; - bool display_horizontal_ = false; - bool did_fetch_ = false; - - int horizontal_offset_ = 0; - enum HorizontalFetch { - Started, WillRequestStop, StopRequested, Stopped - } horizontal_fetch_ = HorizontalFetch::Stopped; - - // Output state. - uint16_t border_colour_ = 0; - bool is_border_ = true; - int zone_duration_ = 0; - uint16_t *pixels_ = nullptr; - uint16_t last_colour_ = 0; // Retained for HAM mode. - void flush_output(); - - Bitplanes bitplanes_; - - BitplaneData next_bitplanes_, previous_bitplanes_; - bool has_next_bitplanes_ = false; - - int odd_priority_ = 0, even_priority_ = 0; - bool even_over_odd_ = false; - bool hold_and_modify_ = false; - bool dual_playfields_ = false; - bool interlace_ = false; - bool is_long_field_ = false; - - BitplaneShifter bitplane_pixels_; - - int odd_delay_ = 0, even_delay_ = 0; - bool is_high_res_ = false; - - // MARK: - Copper. - - Copper copper_; - - // MARK: - Audio. - - Audio audio_; - - // MARK: - Serial port. - - class SerialPort { - public: - void set_control(uint16_t); - void set_data(uint16_t); - uint16_t get_status(); - - private: -// uint16_t value = 0, reload = 0; -// uint16_t shift = 0, receive_shift = 0; -// uint16_t status; - } serial_; - - // MARK: - Pixel output. - - Outputs::CRT::CRT crt_; - uint16_t palette_[32]{}; - uint16_t swizzled_palette_[64]{}; - - // MARK: - Mouse. - private: - Mouse mouse_; + // MARK: - CIAs. +private: + class DiskController; + class CIAAHandler: public MOS::MOS6526::PortHandler { public: - Inputs::Mouse &get_mouse() { - return mouse_; + CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse); + void set_port_output(MOS::MOS6526::Port port, uint8_t value); + uint8_t get_port_input(MOS::MOS6526::Port port); + void set_activity_observer(Activity::Observer *observer); + + // TEMPORARY. + // TODO: generalise mice and joysticks. + // This is a hack. A TEMPORARY HACK. + void set_joystick(Joystick *joystick) { + joystick_ = joystick; } - // MARK: - Joystick. private: - std::vector> joysticks_; - Joystick &joystick(size_t index) const { - return *static_cast(joysticks_[index].get()); - } - - // MARK: - CIAs. - private: - class DiskController; - - class CIAAHandler: public MOS::MOS6526::PortHandler { - public: - CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse); - void set_port_output(MOS::MOS6526::Port port, uint8_t value); - uint8_t get_port_input(MOS::MOS6526::Port port); - void set_activity_observer(Activity::Observer *observer); - - // TEMPORARY. - // TODO: generalise mice and joysticks. - // This is a hack. A TEMPORARY HACK. - void set_joystick(Joystick *joystick) { - joystick_ = joystick; - } - - private: - MemoryMap &map_; - DiskController &controller_; - Mouse &mouse_; - Joystick *joystick_ = nullptr; - Activity::Observer *observer_ = nullptr; - inline static const std::string led_name = "Power"; - } cia_a_handler_; - - class CIABHandler: public MOS::MOS6526::PortHandler { - public: - CIABHandler(DiskController &controller); - void set_port_output(MOS::MOS6526::Port port, uint8_t value); - uint8_t get_port_input(MOS::MOS6526::Port); - - private: - DiskController &controller_; - } cia_b_handler_; + MemoryMap &map_; + DiskController &controller_; + Mouse &mouse_; + Joystick *joystick_ = nullptr; + Activity::Observer *observer_ = nullptr; + inline static const std::string led_name = "Power"; + } cia_a_handler_; + class CIABHandler: public MOS::MOS6526::PortHandler { public: - using CIAA = MOS::MOS6526::MOS6526; - using CIAB = MOS::MOS6526::MOS6526; - - // CIAs are provided for direct access; it's up to the caller properly - // to distinguish relevant accesses. - CIAA cia_a; - CIAB cia_b; + CIABHandler(DiskController &controller); + void set_port_output(MOS::MOS6526::Port port, uint8_t value); + uint8_t get_port_input(MOS::MOS6526::Port); private: - // MARK: - Disk drives. + DiskController &controller_; + } cia_b_handler_; - class DiskDMA: public DMADevice<1> { - public: - using DMADevice::DMADevice; +public: + using CIAA = MOS::MOS6526::MOS6526; + using CIAB = MOS::MOS6526::MOS6526; - void set_length(uint16_t); - void set_control(uint16_t); - bool advance_dma(); + // CIAs are provided for direct access; it's up to the caller properly + // to distinguish relevant accesses. + CIAA cia_a; + CIAB cia_b; - void enqueue(uint16_t value, bool matches_sync); +private: + // MARK: - Disk drives. - private: - uint16_t length_; - bool dma_enable_ = false; - bool write_ = false; - uint16_t last_set_length_ = 0; - bool sync_with_word_ = false; + class DiskDMA: public DMADevice<1> { + public: + using DMADevice::DMADevice; - std::array buffer_; - size_t buffer_read_ = 0, buffer_write_ = 0; + void set_length(uint16_t); + void set_control(uint16_t); + bool advance_dma(); - enum class State { - Inactive, - WaitingForSync, - Reading, - } state_ = State::Inactive; - } disk_; + void enqueue(uint16_t value, bool matches_sync); - class DiskController: public Storage::Disk::Controller { - public: - DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia); + private: + uint16_t length_; + bool dma_enable_ = false; + bool write_ = false; + uint16_t last_set_length_ = 0; + bool sync_with_word_ = false; - void set_mtr_sel_side_dir_step(uint8_t); - uint8_t get_rdy_trk0_wpro_chng(); + std::array buffer_; + size_t buffer_read_ = 0, buffer_write_ = 0; - void run_for(Cycles duration) { - Storage::Disk::Controller::run_for(duration); - } + enum class State { + Inactive, + WaitingForSync, + Reading, + } state_ = State::Inactive; + } disk_; - bool insert(const std::shared_ptr &disk, size_t drive); - void set_activity_observer(Activity::Observer *); + class DiskController: public Storage::Disk::Controller { + public: + DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia); - void set_sync_word(uint16_t); - void set_control(uint16_t); + void set_mtr_sel_side_dir_step(uint8_t); + uint8_t get_rdy_trk0_wpro_chng(); - private: - void process_input_bit(int value) final; - void process_index_hole() final; + void run_for(Cycles duration) { + Storage::Disk::Controller::run_for(duration); + } - // Implement the Amiga's drive ID shift registers - // directly in the controller for now. - uint32_t drive_ids_[4]{}; - uint32_t previous_select_ = 0; + bool insert(const std::shared_ptr &disk, size_t drive); + void set_activity_observer(Activity::Observer *); - uint16_t data_ = 0; - int bit_count_ = 0; - uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess. - bool sync_with_word_ = false; + void set_sync_word(uint16_t); + void set_control(uint16_t); - Chipset &chipset_; - DiskDMA &disk_dma_; - CIAB &cia_; + private: + void process_input_bit(int value) final; + void process_index_hole() final; - } disk_controller_; - friend DiskController; + // Implement the Amiga's drive ID shift registers + // directly in the controller for now. + uint32_t drive_ids_[4]{}; + uint32_t previous_select_ = 0; - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final; - bool disk_controller_is_sleeping_ = false; - uint16_t paula_disk_control_ = 0; + uint16_t data_ = 0; + int bit_count_ = 0; + uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess. + bool sync_with_word_ = false; - // MARK: - Keyboard. + Chipset &chipset_; + DiskDMA &disk_dma_; + CIAB &cia_; + } disk_controller_; + friend DiskController; - Keyboard keyboard_; + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final; + bool disk_controller_is_sleeping_ = false; + uint16_t paula_disk_control_ = 0; + + // MARK: - Keyboard. + + Keyboard keyboard_; }; } diff --git a/Machines/Amiga/Copper.hpp b/Machines/Amiga/Copper.hpp index 5c16912b5..599a9a434 100644 --- a/Machines/Amiga/Copper.hpp +++ b/Machines/Amiga/Copper.hpp @@ -13,39 +13,39 @@ namespace Amiga { class Copper: public DMADevice<2> { - public: - using DMADevice<2>::DMADevice; +public: + using DMADevice<2>::DMADevice; - /// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status. - /// - /// @returns @c true if the slot was used; @c false otherwise. - bool advance_dma(uint16_t position, uint16_t blitter_status); + /// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status. + /// + /// @returns @c true if the slot was used; @c false otherwise. + bool advance_dma(uint16_t position, uint16_t blitter_status); - /// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper. - template void reload() { - address_ = pointer_[id]; - state_ = State::FetchFirstWord; - } + /// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper. + template void reload() { + address_ = pointer_[id]; + state_ = State::FetchFirstWord; + } - /// Sets the Copper control word. - void set_control(uint16_t c) { - control_ = c; - } + /// Sets the Copper control word. + void set_control(uint16_t c) { + control_ = c; + } - /// Forces the Copper into the stopped state. - void stop() { - state_ = State::Stopped; - } + /// Forces the Copper into the stopped state. + void stop() { + state_ = State::Stopped; + } - private: - uint32_t address_ = 0; - uint16_t control_ = 0; +private: + uint32_t address_ = 0; + uint16_t control_ = 0; - enum class State { - FetchFirstWord, FetchSecondWord, Waiting, Stopped, - } state_ = State::Stopped; - bool skip_next_ = false; - uint16_t instruction_[2]{}; + enum class State { + FetchFirstWord, FetchSecondWord, Waiting, Stopped, + } state_ = State::Stopped; + bool skip_next_ = false; + uint16_t instruction_[2]{}; }; } diff --git a/Machines/Amiga/DMADevice.hpp b/Machines/Amiga/DMADevice.hpp index a1ecf6426..020e5299b 100644 --- a/Machines/Amiga/DMADevice.hpp +++ b/Machines/Amiga/DMADevice.hpp @@ -32,40 +32,40 @@ class DMADeviceBase { }; template class DMADevice: public DMADeviceBase { - public: - using DMADeviceBase::DMADeviceBase; +public: + using DMADeviceBase::DMADeviceBase; - /// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first. - template void set_pointer(uint16_t value) { - static_assert(id < num_addresses); - static_assert(shift == 0 || shift == 16); + /// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first. + template void set_pointer(uint16_t value) { + static_assert(id < num_addresses); + static_assert(shift == 0 || shift == 16); - byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift); - pointer_[id] = byte_pointer_[id] >> 1; - } + byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift); + pointer_[id] = byte_pointer_[id] >> 1; + } - /// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first. - template void set_modulo(uint16_t value) { - static_assert(id < num_modulos); + /// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first. + template void set_modulo(uint16_t value) { + static_assert(id < num_modulos); - // Convert by sign extension. - modulos_[id] = uint32_t(int16_t(value) >> 1); - } + // Convert by sign extension. + modulos_[id] = uint32_t(int16_t(value) >> 1); + } - template uint16_t get_pointer() { - // Restore the original least-significant bit. - const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1); - return uint16_t(source >> shift); - } + template uint16_t get_pointer() { + // Restore the original least-significant bit. + const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1); + return uint16_t(source >> shift); + } - protected: - // These are shifted right one to provide word-indexing pointers; - // subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly. - std::array pointer_{}; - std::array modulos_{}; +protected: + // These are shifted right one to provide word-indexing pointers; + // subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly. + std::array pointer_{}; + std::array modulos_{}; - private: - std::array byte_pointer_{}; +private: + std::array byte_pointer_{}; }; } diff --git a/Machines/Amiga/Keyboard.hpp b/Machines/Amiga/Keyboard.hpp index 95a7aa786..e8a968875 100644 --- a/Machines/Amiga/Keyboard.hpp +++ b/Machines/Amiga/Keyboard.hpp @@ -77,42 +77,42 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe }; class Keyboard { - public: - Keyboard(Serial::Line &output); +public: + Keyboard(Serial::Line &output); -// enum Lines: uint8_t { -// Data = (1 << 0), -// Clock = (1 << 1), -// }; +// enum Lines: uint8_t { +// Data = (1 << 0), +// Clock = (1 << 1), +// }; // -// uint8_t update(uint8_t); +// uint8_t update(uint8_t); - void set_key_state(uint16_t, bool); - void clear_all_keys(); + void set_key_state(uint16_t, bool); + void clear_all_keys(); - void run_for(HalfCycles duration) { - output_.advance_writer(duration); - } + void run_for(HalfCycles duration) { + output_.advance_writer(duration); + } - private: -// enum class ShiftState { -// Shifting, -// AwaitingHandshake, -// Idle, -// } shift_state_ = ShiftState::Idle; +private: +// enum class ShiftState { +// Shifting, +// AwaitingHandshake, +// Idle, +// } shift_state_ = ShiftState::Idle; +// +// enum class State { +// Startup, +// } state_ = State::Startup; +// +// int bit_phase_ = 0; +// uint32_t shift_sequence_ = 0; +// int bits_remaining_ = 0; +// +// uint8_t lines_ = 0; -// enum class State { -// Startup, -// } state_ = State::Startup; - -// int bit_phase_ = 0; -// uint32_t shift_sequence_ = 0; -// int bits_remaining_ = 0; - -// uint8_t lines_ = 0; - - Serial::Line &output_; - std::array pressed_{}; + Serial::Line &output_; + std::array pressed_{}; }; } diff --git a/Machines/Amiga/MemoryMap.hpp b/Machines/Amiga/MemoryMap.hpp index d0d2d53bb..a82069350 100644 --- a/Machines/Amiga/MemoryMap.hpp +++ b/Machines/Amiga/MemoryMap.hpp @@ -17,178 +17,178 @@ namespace Amiga { class MemoryMap { - private: - static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead; - static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite; - static constexpr auto PermitReadWrite = PermitRead | PermitWrite; +private: + static constexpr auto PermitRead = CPU::MC68000::Operation::PermitRead; + static constexpr auto PermitWrite = CPU::MC68000::Operation::PermitWrite; + static constexpr auto PermitReadWrite = PermitRead | PermitWrite; - public: - std::array kickstart{0xff}; - std::vector chip_ram{}; +public: + std::array kickstart{0xff}; + std::vector chip_ram{}; - struct MemoryRegion { - uint8_t *contents = nullptr; - unsigned int read_write_mask = 0; - } regions[64]; // i.e. top six bits are used as an index. + struct MemoryRegion { + uint8_t *contents = nullptr; + unsigned int read_write_mask = 0; + } regions[64]; // i.e. top six bits are used as an index. - using FastRAM = Analyser::Static::Amiga::Target::FastRAM; - using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM; - MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) { - // Address spaces that matter: + using FastRAM = Analyser::Static::Amiga::Target::FastRAM; + using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM; + MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) { + // Address spaces that matter: + // + // 00'0000 – 08'0000: chip RAM. [or overlayed KickStart] + // – 10'0000: extended chip ram for ECS. + // – 20'0000: slow RAM and further chip RAM. + // – a0'0000: auto-config space (/fast RAM). + // ... + // bf'd000 – c0'0000: 8250s. + // c0'0000 – d8'0000: pseudo-fast RAM. + // ... + // dc'0000 – dd'0000: optional real-time clock. + // df'f000 - e0'0000: custom chip registers. + // ... + // f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?). + // f8'0000 — : 256kb Kickstart if 2.04 or higher. + // fc'0000 – : 256kb Kickstart otherwise. + set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead); + + switch(chip_ram_size) { + default: + case ChipRAM::FiveHundredAndTwelveKilobytes: + chip_ram.resize(512 * 1024); + break; + case ChipRAM::OneMegabyte: + chip_ram.resize(1 * 1024 * 1024); + break; + case ChipRAM::TwoMegabytes: + chip_ram.resize(2 * 1024 * 1024); + break; + } + + switch(fast_ram_size) { + default: + fast_autoconf_visible_ = false; + break; + case FastRAM::OneMegabyte: + fast_ram_.resize(1 * 1024 * 1024); + fast_ram_size_ = 5; + break; + case FastRAM::TwoMegabytes: + fast_ram_.resize(2 * 1024 * 1024); + fast_ram_size_ = 6; + break; + case FastRAM::FourMegabytes: + fast_ram_.resize(4 * 1024 * 1024); + fast_ram_size_ = 7; + break; + case FastRAM::EightMegabytes: + fast_ram_.resize(8 * 1024 * 1024); + fast_ram_size_ = 0; + break; + } + + reset(); + } + + void reset() { + set_overlay(true); + } + + void set_overlay(bool enabled) { + if(overlay_ == enabled) { + return; + } + overlay_ = enabled; + + set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite); + if(enabled) { + set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead); + } + } + + /// Performs the provided microcycle, which the caller guarantees to be a memory access, + /// and in the Zorro register range. + template + bool perform(const Microcycle &cycle) { + if(!fast_autoconf_visible_) return false; + + const uint32_t register_address = *cycle.address & 0xfe; + + if(cycle.operation & CPU::MC68000::Operation::Read) { + // Re: Autoconf: // - // 00'0000 – 08'0000: chip RAM. [or overlayed KickStart] - // – 10'0000: extended chip ram for ECS. - // – 20'0000: slow RAM and further chip RAM. - // – a0'0000: auto-config space (/fast RAM). - // ... - // bf'd000 – c0'0000: 8250s. - // c0'0000 – d8'0000: pseudo-fast RAM. - // ... - // dc'0000 – dd'0000: optional real-time clock. - // df'f000 - e0'0000: custom chip registers. - // ... - // f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?). - // f8'0000 — : 256kb Kickstart if 2.04 or higher. - // fc'0000 – : 256kb Kickstart otherwise. - set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead); + // "All read registers physically return only the top 4 bits of data, on D31-D28"; + // (this is from Zorro III documentation; I'm assuming it to be D15–D11 for the + // 68000's 16-bit bus); + // + // "Every AUTOCONFIG register is logically considered to be 8 bits wide; the + // 8 bits actually being nybbles from two paired addresses." - switch(chip_ram_size) { - default: - case ChipRAM::FiveHundredAndTwelveKilobytes: - chip_ram.resize(512 * 1024); + uint8_t value = 0xf; + switch(register_address) { + default: break; + + case 0x00: // er_Type (high) + value = + 0xc | // Zoro II-style PIC. + 0x2; // Memory will be linked into the free pool break; - case ChipRAM::OneMegabyte: - chip_ram.resize(1 * 1024 * 1024); + case 0x02: // er_Type (low) + value = fast_ram_size_; break; - case ChipRAM::TwoMegabytes: - chip_ram.resize(2 * 1024 * 1024); + + // er_Manufacturer + // + // On the manufacturer number: this is supposed to be assigned + // by Commodore. TODO: find and crib a real fast RAM number, if it matters. + // + // (0xffff seems to be invalid, so _something_ needs to be supplied) + case 0x10: case 0x12: + value = 0xa; // Manufacturer's number, high byte. + break; + case 0x14: case 0x16: + value = 0xb; // Manufacturer's number, low byte. break; } - switch(fast_ram_size) { - default: + // Shove the value into the top of the data bus. + cycle.set_value16(uint16_t(0x0fff | (value << 12))); + } else { + fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50); + + switch(register_address) { + default: break; + + case 0x48: { // ec_BaseAddress (A23–A16) + const auto address = uint32_t(cycle.value8_high()) << 16; + set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite); fast_autoconf_visible_ = false; - break; - case FastRAM::OneMegabyte: - fast_ram_.resize(1 * 1024 * 1024); - fast_ram_size_ = 5; - break; - case FastRAM::TwoMegabytes: - fast_ram_.resize(2 * 1024 * 1024); - fast_ram_size_ = 6; - break; - case FastRAM::FourMegabytes: - fast_ram_.resize(4 * 1024 * 1024); - fast_ram_size_ = 7; - break; - case FastRAM::EightMegabytes: - fast_ram_.resize(8 * 1024 * 1024); - fast_ram_size_ = 0; - break; - } - - reset(); - } - - void reset() { - set_overlay(true); - } - - void set_overlay(bool enabled) { - if(overlay_ == enabled) { - return; - } - overlay_ = enabled; - - set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite); - if(enabled) { - set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead); + } break; } } - /// Performs the provided microcycle, which the caller guarantees to be a memory access, - /// and in the Zorro register range. - template - bool perform(const Microcycle &cycle) { - if(!fast_autoconf_visible_) return false; + return true; + } - const uint32_t register_address = *cycle.address & 0xfe; +private: + std::vector fast_ram_{}; + uint8_t fast_ram_size_ = 0; - if(cycle.operation & CPU::MC68000::Operation::Read) { - // Re: Autoconf: - // - // "All read registers physically return only the top 4 bits of data, on D31-D28"; - // (this is from Zorro III documentation; I'm assuming it to be D15–D11 for the - // 68000's 16-bit bus); - // - // "Every AUTOCONFIG register is logically considered to be 8 bits wide; the - // 8 bits actually being nybbles from two paired addresses." + bool fast_autoconf_visible_ = true; + bool overlay_ = false; - uint8_t value = 0xf; - switch(register_address) { - default: break; + void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) { + [[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000); + assert(!(start & precision_loss_mask)); + assert(!((end - (1 << 18)) & precision_loss_mask)); + assert(end > start); - case 0x00: // er_Type (high) - value = - 0xc | // Zoro II-style PIC. - 0x2; // Memory will be linked into the free pool - break; - case 0x02: // er_Type (low) - value = fast_ram_size_; - break; - - // er_Manufacturer - // - // On the manufacturer number: this is supposed to be assigned - // by Commodore. TODO: find and crib a real fast RAM number, if it matters. - // - // (0xffff seems to be invalid, so _something_ needs to be supplied) - case 0x10: case 0x12: - value = 0xa; // Manufacturer's number, high byte. - break; - case 0x14: case 0x16: - value = 0xb; // Manufacturer's number, low byte. - break; - } - - // Shove the value into the top of the data bus. - cycle.set_value16(uint16_t(0x0fff | (value << 12))); - } else { - fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50); - - switch(register_address) { - default: break; - - case 0x48: { // ec_BaseAddress (A23–A16) - const auto address = uint32_t(cycle.value8_high()) << 16; - set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite); - fast_autoconf_visible_ = false; - } break; - } - } - - return true; - } - - private: - std::vector fast_ram_{}; - uint8_t fast_ram_size_ = 0; - - bool fast_autoconf_visible_ = true; - bool overlay_ = false; - - void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) { - [[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000); - assert(!(start & precision_loss_mask)); - assert(!((end - (1 << 18)) & precision_loss_mask)); - assert(end > start); - - if(base) base -= start; - for(decltype(start) c = start >> 18; c < end >> 18; c++) { - regions[c].contents = base; - regions[c].read_write_mask = read_write_mask; - } + if(base) base -= start; + for(decltype(start) c = start >> 18; c < end >> 18; c++) { + regions[c].contents = base; + regions[c].read_write_mask = read_write_mask; } + } }; } diff --git a/Machines/Amiga/MouseJoystick.hpp b/Machines/Amiga/MouseJoystick.hpp index fa667c505..50da9d59a 100644 --- a/Machines/Amiga/MouseJoystick.hpp +++ b/Machines/Amiga/MouseJoystick.hpp @@ -22,33 +22,33 @@ struct MouseJoystickInput { }; class Mouse: public Inputs::Mouse, public MouseJoystickInput { - public: - uint16_t get_position() final; - uint8_t get_cia_button() const final; +public: + uint16_t get_position() final; + uint8_t get_cia_button() const final; - private: - int get_number_of_buttons() const final; - void set_button_pressed(int, bool) final; - void reset_all_buttons() final; - void move(int, int) final; +private: + int get_number_of_buttons() const final; + void set_button_pressed(int, bool) final; + void reset_all_buttons() final; + void move(int, int) final; - uint8_t declared_position_[2]{}; - uint8_t cia_state_ = 0xff; - std::array, 2> position_{}; + uint8_t declared_position_[2]{}; + uint8_t cia_state_ = 0xff; + std::array, 2> position_{}; }; class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput { - public: - Joystick(); +public: + Joystick(); - uint16_t get_position() final; - uint8_t get_cia_button() const final; + uint16_t get_position() final; + uint8_t get_cia_button() const final; - private: - void did_set_input(const Input &input, bool is_active) final; +private: + void did_set_input(const Input &input, bool is_active) final; - bool inputs_[Joystick::Input::Type::Max]{}; - uint16_t position_ = 0; + bool inputs_[Joystick::Input::Type::Max]{}; + uint16_t position_ = 0; }; } diff --git a/Machines/Amiga/Sprites.hpp b/Machines/Amiga/Sprites.hpp index 1ce95ed5d..5f150a378 100644 --- a/Machines/Amiga/Sprites.hpp +++ b/Machines/Amiga/Sprites.hpp @@ -15,52 +15,52 @@ namespace Amiga { class Sprite: public DMADevice<1> { - public: - using DMADevice::DMADevice; +public: + using DMADevice::DMADevice; - void set_start_position(uint16_t value); - void set_stop_and_control(uint16_t value); - void set_image_data(int slot, uint16_t value); + void set_start_position(uint16_t value); + void set_stop_and_control(uint16_t value); + void set_image_data(int slot, uint16_t value); - bool advance_dma(int offset, int y, bool is_first_line); + bool advance_dma(int offset, int y, bool is_first_line); - uint16_t data[2]{}; - bool attached = false; - bool visible = false; - uint16_t h_start = 0; + uint16_t data[2]{}; + bool attached = false; + bool visible = false; + uint16_t h_start = 0; - private: - uint16_t v_start_ = 0, v_stop_ = 0; +private: + uint16_t v_start_ = 0, v_stop_ = 0; }; class TwoSpriteShifter { - public: - /// Installs new pixel data for @c sprite (either 0 or 1), - /// with @c delay being either 0 or 1 to indicate whether - /// output should begin now or in one pixel's time. - template void load( - uint16_t lsb, - uint16_t msb, - int delay); +public: + /// Installs new pixel data for @c sprite (either 0 or 1), + /// with @c delay being either 0 or 1 to indicate whether + /// output should begin now or in one pixel's time. + template void load( + uint16_t lsb, + uint16_t msb, + int delay); - /// Shifts two pixels. - void shift() { - data_ <<= 8; - data_ |= overflow_; - overflow_ = 0; - } + /// Shifts two pixels. + void shift() { + data_ <<= 8; + data_ |= overflow_; + overflow_ = 0; + } - /// @returns The next two pixels to output, formulated as - /// abcd efgh where ab and ef are two pixels of the first sprite - /// and cd and gh are two pixels of the second. In each case the - /// more significant two are output first. - uint8_t get() { - return uint8_t(data_ >> 56); - } + /// @returns The next two pixels to output, formulated as + /// abcd efgh where ab and ef are two pixels of the first sprite + /// and cd and gh are two pixels of the second. In each case the + /// more significant two are output first. + uint8_t get() { + return uint8_t(data_ >> 56); + } - private: - uint64_t data_; - uint8_t overflow_; +private: + uint64_t data_; + uint8_t overflow_; }; } diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 17328b5d8..04ded757c 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -48,67 +48,67 @@ namespace AmstradCPC { Hsync and vsync signals are expected to come directly from the CRTC; they are not decoded from a composite stream. */ class InterruptTimer { - public: - /*! - Indicates that a new hsync pulse has been recognised. This should be - supplied on the falling edge of the CRTC HSYNC signal, which is the - trailing edge because it is active high. - */ - inline void signal_hsync() { - // Increment the timer and if it has hit 52 then reset it and - // set the interrupt request line to true. - ++timer_; - if(timer_ == 52) { - timer_ = 0; - interrupt_request_ = true; - } - - // If a vertical sync has previously been indicated then after two - // further horizontal syncs the timer should either (i) set the interrupt - // line, if bit 4 is clear; or (ii) reset the timer. - if(reset_counter_) { - --reset_counter_; - if(!reset_counter_) { - if(timer_ & 32) { - interrupt_request_ = true; - } - timer_ = 0; - } - } - } - - /// Indicates the leading edge of a new vertical sync. - inline void set_vsync(bool active) { - reset_counter_ = active ? 2 : 0; - } - - /// Indicates that an interrupt acknowledge has been received from the Z80. - inline void signal_interrupt_acknowledge() { - interrupt_request_ = false; - timer_ &= ~32; - } - - /// @returns @c true if an interrupt is currently requested; @c false otherwise. - inline bool get_request() { - return last_interrupt_request_ = interrupt_request_; - } - - /// Asks whether the interrupt status has changed since the last call to @c get_request(). - inline bool request_has_changed() { - return last_interrupt_request_ != interrupt_request_; - } - - /// Resets the timer. - inline void reset_count() { +public: + /*! + Indicates that a new hsync pulse has been recognised. This should be + supplied on the falling edge of the CRTC HSYNC signal, which is the + trailing edge because it is active high. + */ + inline void signal_hsync() { + // Increment the timer and if it has hit 52 then reset it and + // set the interrupt request line to true. + ++timer_; + if(timer_ == 52) { timer_ = 0; - interrupt_request_ = false; + interrupt_request_ = true; } - private: - int reset_counter_ = 0; - bool interrupt_request_ = false; - bool last_interrupt_request_ = false; - int timer_ = 0; + // If a vertical sync has previously been indicated then after two + // further horizontal syncs the timer should either (i) set the interrupt + // line, if bit 4 is clear; or (ii) reset the timer. + if(reset_counter_) { + --reset_counter_; + if(!reset_counter_) { + if(timer_ & 32) { + interrupt_request_ = true; + } + timer_ = 0; + } + } + } + + /// Indicates the leading edge of a new vertical sync. + inline void set_vsync(bool active) { + reset_counter_ = active ? 2 : 0; + } + + /// Indicates that an interrupt acknowledge has been received from the Z80. + inline void signal_interrupt_acknowledge() { + interrupt_request_ = false; + timer_ &= ~32; + } + + /// @returns @c true if an interrupt is currently requested; @c false otherwise. + inline bool get_request() { + return last_interrupt_request_ = interrupt_request_; + } + + /// Asks whether the interrupt status has changed since the last call to @c get_request(). + inline bool request_has_changed() { + return last_interrupt_request_ != interrupt_request_; + } + + /// Resets the timer. + inline void reset_count() { + timer_ = 0; + interrupt_request_ = false; + } + +private: + int reset_counter_ = 0; + bool interrupt_request_ = false; + bool last_interrupt_request_ = false; + int timer_ = 0; }; /*! @@ -117,49 +117,49 @@ class InterruptTimer { deferred clocking for this component. */ class AYDeferrer { - public: - /// Constructs a new AY instance and sets its clock rate. - AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { - speaker_.set_input_rate(1000000); - // Per the CPC Wiki: - // "A is output to the right, channel C is output left, and channel B is output to both left and right". - ay_.set_output_mixing(0.0, 0.5, 1.0, 1.0, 0.5, 0.0); - } +public: + /// Constructs a new AY instance and sets its clock rate. + AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) { + speaker_.set_input_rate(1000000); + // Per the CPC Wiki: + // "A is output to the right, channel C is output left, and channel B is output to both left and right". + ay_.set_output_mixing(0.0, 0.5, 1.0, 1.0, 0.5, 0.0); + } - ~AYDeferrer() { - audio_queue_.flush(); - } + ~AYDeferrer() { + audio_queue_.flush(); + } - /// Adds @c half_cycles half cycles to the amount of time that has passed. - inline void run_for(HalfCycles half_cycles) { - cycles_since_update_ += half_cycles; - } + /// Adds @c half_cycles half cycles to the amount of time that has passed. + inline void run_for(HalfCycles half_cycles) { + cycles_since_update_ += half_cycles; + } - /// Enqueues an update-to-now into the AY's deferred queue. - inline void update() { - speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4))); - } + /// Enqueues an update-to-now into the AY's deferred queue. + inline void update() { + speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4))); + } - /// Issues a request to the AY to perform all processing up to the current time. - inline void flush() { - audio_queue_.perform(); - } + /// Issues a request to the AY to perform all processing up to the current time. + inline void flush() { + audio_queue_.perform(); + } - /// @returns the speaker the AY is using for output. - Outputs::Speaker::Speaker *get_speaker() { - return &speaker_; - } + /// @returns the speaker the AY is using for output. + Outputs::Speaker::Speaker *get_speaker() { + return &speaker_; + } - /// @returns the AY itself. - GI::AY38910::AY38910 &ay() { - return ay_; - } + /// @returns the AY itself. + GI::AY38910::AY38910 &ay() { + return ay_; + } - private: - Concurrency::AsyncTaskQueue audio_queue_; - GI::AY38910::AY38910 ay_; - Outputs::Speaker::PullLowpass> speaker_; - HalfCycles cycles_since_update_; +private: + Concurrency::AsyncTaskQueue audio_queue_; + GI::AY38910::AY38910 ay_; + Outputs::Speaker::PullLowpass> speaker_; + HalfCycles cycles_since_update_; }; /*! @@ -168,223 +168,223 @@ class AYDeferrer { generation and therefore owns details such as the current palette. */ class CRTCBusHandler { - public: - CRTCBusHandler(const uint8_t *ram, InterruptTimer &interrupt_timer) : - crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2), - ram_(ram), - interrupt_timer_(interrupt_timer) { - establish_palette_hits(); - build_mode_table(); - crt_.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f)); - crt_.set_brightness(3.0f / 2.0f); // As only the values 0, 1 and 2 will be used in each channel, - // whereas Red2Green2Blue2 defines a range of 0-3. - } +public: + CRTCBusHandler(const uint8_t *ram, InterruptTimer &interrupt_timer) : + crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2), + ram_(ram), + interrupt_timer_(interrupt_timer) { + establish_palette_hits(); + build_mode_table(); + crt_.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f)); + crt_.set_brightness(3.0f / 2.0f); // As only the values 0, 1 and 2 will be used in each channel, + // whereas Red2Green2Blue2 defines a range of 0-3. + } - /*! - The CRTC entry function for the main part of each clock cycle; takes the current - bus state and determines what output to produce based on the current palette and mode. - */ - forceinline void perform_bus_cycle(const Motorola::CRTC::BusState &state) { - // TODO: there's a one-tick delay on pixel output; incorporate that. + /*! + The CRTC entry function for the main part of each clock cycle; takes the current + bus state and determines what output to produce based on the current palette and mode. + */ + forceinline void perform_bus_cycle(const Motorola::CRTC::BusState &state) { + // TODO: there's a one-tick delay on pixel output; incorporate that. - // The gate array waits 2us to react to the CRTC's vsync signal, and then - // caps output at 4us. Since the clock rate is 1Mhz, that's 2 and 4 cycles, - // respectively. - if(state.hsync) { - cycles_into_hsync_++; - } else { - cycles_into_hsync_ = 0; - } + // The gate array waits 2us to react to the CRTC's vsync signal, and then + // caps output at 4us. Since the clock rate is 1Mhz, that's 2 and 4 cycles, + // respectively. + if(state.hsync) { + cycles_into_hsync_++; + } else { + cycles_into_hsync_ = 0; + } - const bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6); - const bool is_colour_burst = (cycles_into_hsync_ >= 7 && cycles_into_hsync_ < 11); + const bool is_hsync = (cycles_into_hsync_ >= 2 && cycles_into_hsync_ < 6); + const bool is_colour_burst = (cycles_into_hsync_ >= 7 && cycles_into_hsync_ < 11); - // Sync is taken to override pixels, and is combined as a simple OR. - const bool is_sync = is_hsync || state.vsync; - const bool is_blank = !is_sync && state.hsync; + // Sync is taken to override pixels, and is combined as a simple OR. + const bool is_sync = is_hsync || state.vsync; + const bool is_blank = !is_sync && state.hsync; - OutputMode output_mode; - if(is_sync) { - output_mode = OutputMode::Sync; - } else if(is_colour_burst) { - output_mode = OutputMode::ColourBurst; - } else if(is_blank) { - output_mode = OutputMode::Blank; - } else if(state.display_enable) { - output_mode = OutputMode::Pixels; - } else { - output_mode = OutputMode::Border; - } + OutputMode output_mode; + if(is_sync) { + output_mode = OutputMode::Sync; + } else if(is_colour_burst) { + output_mode = OutputMode::ColourBurst; + } else if(is_blank) { + output_mode = OutputMode::Blank; + } else if(state.display_enable) { + output_mode = OutputMode::Pixels; + } else { + output_mode = OutputMode::Border; + } - // If a transition between sync/border/pixels just occurred, flush whatever was - // in progress to the CRT and reset counting. - if(output_mode != previous_output_mode_) { - if(cycles_) { - switch(previous_output_mode_) { - default: - case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break; - case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break; - case OutputMode::Border: output_border(cycles_); break; - case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break; - case OutputMode::Pixels: - crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); - pixel_pointer_ = pixel_data_ = nullptr; - break; - } - } - - cycles_ = 0; - previous_output_mode_ = output_mode; - } - - // Increment cycles since state changed. - cycles_++; - - // Collect some more pixels if output is ongoing. - if(previous_output_mode_ == OutputMode::Pixels) { - if(!pixel_data_) { - pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8); - } - if(pixel_pointer_) { - // the CPC shuffles output lines as: - // MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK - // ... so form the real access address. - const uint16_t address = - uint16_t( - ((state.refresh_address & 0x3ff) << 1) | - ((state.row_address & 0x7) << 11) | - ((state.refresh_address & 0x3000) << 2) - ); - - // Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at - // hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without - // exactly reaching 320 output pixels. - switch(mode_) { - case 0: - reinterpret_cast(pixel_pointer_)[0] = mode0_output_[ram_[address]]; - reinterpret_cast(pixel_pointer_)[1] = mode0_output_[ram_[address+1]]; - pixel_pointer_ += 2 * sizeof(uint16_t); - break; - - case 1: - reinterpret_cast(pixel_pointer_)[0] = mode1_output_[ram_[address]]; - reinterpret_cast(pixel_pointer_)[1] = mode1_output_[ram_[address+1]]; - pixel_pointer_ += 2 * sizeof(uint32_t); - break; - - case 2: - reinterpret_cast(pixel_pointer_)[0] = mode2_output_[ram_[address]]; - reinterpret_cast(pixel_pointer_)[1] = mode2_output_[ram_[address+1]]; - pixel_pointer_ += 2 * sizeof(uint64_t); - break; - - case 3: - reinterpret_cast(pixel_pointer_)[0] = mode3_output_[ram_[address]]; - reinterpret_cast(pixel_pointer_)[1] = mode3_output_[ram_[address+1]]; - pixel_pointer_ += 2 * sizeof(uint16_t); - break; - - } - - // Flush the current buffer pixel if full; the CRTC allows many different display - // widths so it's not necessarily possible to predict the correct number in advance - // and using the upper bound could lead to inefficient behaviour. - if(pixel_pointer_ == pixel_data_ + 320) { + // If a transition between sync/border/pixels just occurred, flush whatever was + // in progress to the CRT and reset counting. + if(output_mode != previous_output_mode_) { + if(cycles_) { + switch(previous_output_mode_) { + default: + case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break; + case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break; + case OutputMode::Border: output_border(cycles_); break; + case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break; + case OutputMode::Pixels: crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); pixel_pointer_ = pixel_data_ = nullptr; - cycles_ = 0; - } + break; } } - // Latch mode four cycles after HSYNC was signalled, if still active. - if(cycles_into_hsync_ == 4 && mode_ != next_mode_) { - mode_ = next_mode_; + cycles_ = 0; + previous_output_mode_ = output_mode; + } + + // Increment cycles since state changed. + cycles_++; + + // Collect some more pixels if output is ongoing. + if(previous_output_mode_ == OutputMode::Pixels) { + if(!pixel_data_) { + pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8); + } + if(pixel_pointer_) { + // the CPC shuffles output lines as: + // MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK + // ... so form the real access address. + const uint16_t address = + uint16_t( + ((state.refresh_address & 0x3ff) << 1) | + ((state.row_address & 0x7) << 11) | + ((state.refresh_address & 0x3000) << 2) + ); + + // Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at + // hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without + // exactly reaching 320 output pixels. switch(mode_) { - default: - case 0: pixel_divider_ = 4; break; - case 1: pixel_divider_ = 2; break; - case 2: pixel_divider_ = 1; break; + case 0: + reinterpret_cast(pixel_pointer_)[0] = mode0_output_[ram_[address]]; + reinterpret_cast(pixel_pointer_)[1] = mode0_output_[ram_[address+1]]; + pixel_pointer_ += 2 * sizeof(uint16_t); + break; + + case 1: + reinterpret_cast(pixel_pointer_)[0] = mode1_output_[ram_[address]]; + reinterpret_cast(pixel_pointer_)[1] = mode1_output_[ram_[address+1]]; + pixel_pointer_ += 2 * sizeof(uint32_t); + break; + + case 2: + reinterpret_cast(pixel_pointer_)[0] = mode2_output_[ram_[address]]; + reinterpret_cast(pixel_pointer_)[1] = mode2_output_[ram_[address+1]]; + pixel_pointer_ += 2 * sizeof(uint64_t); + break; + + case 3: + reinterpret_cast(pixel_pointer_)[0] = mode3_output_[ram_[address]]; + reinterpret_cast(pixel_pointer_)[1] = mode3_output_[ram_[address+1]]; + pixel_pointer_ += 2 * sizeof(uint16_t); + break; + } - build_mode_table(); - } - // For the interrupt timer: notify the leading edge of vertical sync and the - // trailing edge of horizontal sync. - if(was_vsync_ != state.vsync) { - interrupt_timer_.set_vsync(state.vsync); - } - if(was_hsync_ && !state.hsync) { - interrupt_timer_.signal_hsync(); - } - - // Update current state for edge detection next time around. - was_vsync_ = state.vsync; - was_hsync_ = state.hsync; - } - - /// Sets the destination for output. - void set_scan_target(Outputs::Display::ScanTarget *scan_target) { - crt_.set_scan_target(scan_target); - } - - /// @returns The current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const { - return crt_.get_scaled_scan_status() / 4.0f; - } - - /// Sets the type of display. - void set_display_type(Outputs::Display::DisplayType display_type) { - crt_.set_display_type(display_type); - } - - /// Gets the type of display. - Outputs::Display::DisplayType get_display_type() const { - return crt_.get_display_type(); - } - - /*! - Sets the next video mode. Per the documentation, mode changes take effect only at the end of line, - not immediately. So next means "as of the end of this line". - */ - void set_next_mode(int mode) { - next_mode_ = mode; - } - - /// Palette management: selects a pen to modify. - void select_pen(int pen) { - pen_ = pen; - } - - /// Palette management: sets the colour of the selected pen. - void set_colour(uint8_t colour) { - if(pen_ & 16) { - // If border is[/was] currently being output, flush what should have been - // drawn in the old colour. - if(previous_output_mode_ == OutputMode::Border) { - output_border(cycles_); + // Flush the current buffer pixel if full; the CRTC allows many different display + // widths so it's not necessarily possible to predict the correct number in advance + // and using the upper bound could lead to inefficient behaviour. + if(pixel_pointer_ == pixel_data_ + 320) { + crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_)); + pixel_pointer_ = pixel_data_ = nullptr; cycles_ = 0; } - border_ = mapped_palette_value(colour); - } else { - palette_[pen_] = mapped_palette_value(colour); - patch_mode_table(size_t(pen_)); } } - private: - void output_border(int length) { - assert(length >= 0); - - // A black border can be output via crt_.output_blank for a minor performance - // win; otherwise paint whatever the border colour really is. - if(border_) { - crt_.output_level(length * 16, border_); - } else { - crt_.output_blank(length * 16); + // Latch mode four cycles after HSYNC was signalled, if still active. + if(cycles_into_hsync_ == 4 && mode_ != next_mode_) { + mode_ = next_mode_; + switch(mode_) { + default: + case 0: pixel_divider_ = 4; break; + case 1: pixel_divider_ = 2; break; + case 2: pixel_divider_ = 1; break; } + build_mode_table(); } + // For the interrupt timer: notify the leading edge of vertical sync and the + // trailing edge of horizontal sync. + if(was_vsync_ != state.vsync) { + interrupt_timer_.set_vsync(state.vsync); + } + if(was_hsync_ && !state.hsync) { + interrupt_timer_.signal_hsync(); + } + + // Update current state for edge detection next time around. + was_vsync_ = state.vsync; + was_hsync_ = state.hsync; + } + + /// Sets the destination for output. + void set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); + } + + /// @returns The current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const { + return crt_.get_scaled_scan_status() / 4.0f; + } + + /// Sets the type of display. + void set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); + } + + /// Gets the type of display. + Outputs::Display::DisplayType get_display_type() const { + return crt_.get_display_type(); + } + + /*! + Sets the next video mode. Per the documentation, mode changes take effect only at the end of line, + not immediately. So next means "as of the end of this line". + */ + void set_next_mode(int mode) { + next_mode_ = mode; + } + + /// Palette management: selects a pen to modify. + void select_pen(int pen) { + pen_ = pen; + } + + /// Palette management: sets the colour of the selected pen. + void set_colour(uint8_t colour) { + if(pen_ & 16) { + // If border is[/was] currently being output, flush what should have been + // drawn in the old colour. + if(previous_output_mode_ == OutputMode::Border) { + output_border(cycles_); + cycles_ = 0; + } + border_ = mapped_palette_value(colour); + } else { + palette_[pen_] = mapped_palette_value(colour); + patch_mode_table(size_t(pen_)); + } + } + +private: + void output_border(int length) { + assert(length >= 0); + + // A black border can be output via crt_.output_blank for a minor performance + // win; otherwise paint whatever the border colour really is. + if(border_) { + crt_.output_level(length * 16, border_); + } else { + crt_.output_blank(length * 16); + } + } + #define Mode0Colour0(c) (((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)) #define Mode0Colour1(c) (((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)) @@ -396,120 +396,120 @@ class CRTCBusHandler { #define Mode3Colour0(c) (((c & 0x80) >> 7) | ((c & 0x08) >> 2)) #define Mode3Colour1(c) (((c & 0x40) >> 6) | ((c & 0x04) >> 1)) - /*! - Creates a lookup table from palette entry to list of affected entries in the value -> pixels lookup tables. - */ - void establish_palette_hits() { - for(size_t c = 0; c < 256; c++) { - assert(Mode0Colour0(c) < mode0_palette_hits_.size()); - assert(Mode0Colour1(c) < mode0_palette_hits_.size()); - mode0_palette_hits_[Mode0Colour0(c)].push_back(uint8_t(c)); - mode0_palette_hits_[Mode0Colour1(c)].push_back(uint8_t(c)); + /*! + Creates a lookup table from palette entry to list of affected entries in the value -> pixels lookup tables. + */ + void establish_palette_hits() { + for(size_t c = 0; c < 256; c++) { + assert(Mode0Colour0(c) < mode0_palette_hits_.size()); + assert(Mode0Colour1(c) < mode0_palette_hits_.size()); + mode0_palette_hits_[Mode0Colour0(c)].push_back(uint8_t(c)); + mode0_palette_hits_[Mode0Colour1(c)].push_back(uint8_t(c)); - assert(Mode1Colour0(c) < mode1_palette_hits_.size()); - assert(Mode1Colour1(c) < mode1_palette_hits_.size()); - assert(Mode1Colour2(c) < mode1_palette_hits_.size()); - assert(Mode1Colour3(c) < mode1_palette_hits_.size()); - mode1_palette_hits_[Mode1Colour0(c)].push_back(uint8_t(c)); - mode1_palette_hits_[Mode1Colour1(c)].push_back(uint8_t(c)); - mode1_palette_hits_[Mode1Colour2(c)].push_back(uint8_t(c)); - mode1_palette_hits_[Mode1Colour3(c)].push_back(uint8_t(c)); + assert(Mode1Colour0(c) < mode1_palette_hits_.size()); + assert(Mode1Colour1(c) < mode1_palette_hits_.size()); + assert(Mode1Colour2(c) < mode1_palette_hits_.size()); + assert(Mode1Colour3(c) < mode1_palette_hits_.size()); + mode1_palette_hits_[Mode1Colour0(c)].push_back(uint8_t(c)); + mode1_palette_hits_[Mode1Colour1(c)].push_back(uint8_t(c)); + mode1_palette_hits_[Mode1Colour2(c)].push_back(uint8_t(c)); + mode1_palette_hits_[Mode1Colour3(c)].push_back(uint8_t(c)); - assert(Mode3Colour0(c) < mode3_palette_hits_.size()); - assert(Mode3Colour1(c) < mode3_palette_hits_.size()); - mode3_palette_hits_[Mode3Colour0(c)].push_back(uint8_t(c)); - mode3_palette_hits_[Mode3Colour1(c)].push_back(uint8_t(c)); - } + assert(Mode3Colour0(c) < mode3_palette_hits_.size()); + assert(Mode3Colour1(c) < mode3_palette_hits_.size()); + mode3_palette_hits_[Mode3Colour0(c)].push_back(uint8_t(c)); + mode3_palette_hits_[Mode3Colour1(c)].push_back(uint8_t(c)); } + } - void build_mode_table() { - switch(mode_) { - case 0: - // Mode 0: abcdefgh -> [gcea] [hdfb] - for(size_t c = 0; c < 256; c++) { - // Prepare mode 0. - uint8_t *const mode0_pixels = reinterpret_cast(&mode0_output_[c]); - mode0_pixels[0] = palette_[Mode0Colour0(c)]; - mode0_pixels[1] = palette_[Mode0Colour1(c)]; - } - break; + void build_mode_table() { + switch(mode_) { + case 0: + // Mode 0: abcdefgh -> [gcea] [hdfb] + for(size_t c = 0; c < 256; c++) { + // Prepare mode 0. + uint8_t *const mode0_pixels = reinterpret_cast(&mode0_output_[c]); + mode0_pixels[0] = palette_[Mode0Colour0(c)]; + mode0_pixels[1] = palette_[Mode0Colour1(c)]; + } + break; - case 1: - for(size_t c = 0; c < 256; c++) { - // Prepare mode 1. - uint8_t *const mode1_pixels = reinterpret_cast(&mode1_output_[c]); - mode1_pixels[0] = palette_[Mode1Colour0(c)]; - mode1_pixels[1] = palette_[Mode1Colour1(c)]; - mode1_pixels[2] = palette_[Mode1Colour2(c)]; - mode1_pixels[3] = palette_[Mode1Colour3(c)]; - } - break; + case 1: + for(size_t c = 0; c < 256; c++) { + // Prepare mode 1. + uint8_t *const mode1_pixels = reinterpret_cast(&mode1_output_[c]); + mode1_pixels[0] = palette_[Mode1Colour0(c)]; + mode1_pixels[1] = palette_[Mode1Colour1(c)]; + mode1_pixels[2] = palette_[Mode1Colour2(c)]; + mode1_pixels[3] = palette_[Mode1Colour3(c)]; + } + break; - case 2: - for(size_t c = 0; c < 256; c++) { - // Prepare mode 2. - uint8_t *const mode2_pixels = reinterpret_cast(&mode2_output_[c]); - mode2_pixels[0] = palette_[((c & 0x80) >> 7)]; - mode2_pixels[1] = palette_[((c & 0x40) >> 6)]; - mode2_pixels[2] = palette_[((c & 0x20) >> 5)]; - mode2_pixels[3] = palette_[((c & 0x10) >> 4)]; - mode2_pixels[4] = palette_[((c & 0x08) >> 3)]; - mode2_pixels[5] = palette_[((c & 0x04) >> 2)]; - mode2_pixels[6] = palette_[((c & 0x03) >> 1)]; - mode2_pixels[7] = palette_[((c & 0x01) >> 0)]; - } - break; + case 2: + for(size_t c = 0; c < 256; c++) { + // Prepare mode 2. + uint8_t *const mode2_pixels = reinterpret_cast(&mode2_output_[c]); + mode2_pixels[0] = palette_[((c & 0x80) >> 7)]; + mode2_pixels[1] = palette_[((c & 0x40) >> 6)]; + mode2_pixels[2] = palette_[((c & 0x20) >> 5)]; + mode2_pixels[3] = palette_[((c & 0x10) >> 4)]; + mode2_pixels[4] = palette_[((c & 0x08) >> 3)]; + mode2_pixels[5] = palette_[((c & 0x04) >> 2)]; + mode2_pixels[6] = palette_[((c & 0x03) >> 1)]; + mode2_pixels[7] = palette_[((c & 0x01) >> 0)]; + } + break; - case 3: - for(size_t c = 0; c < 256; c++) { - // Prepare mode 3. - uint8_t *const mode3_pixels = reinterpret_cast(&mode3_output_[c]); - mode3_pixels[0] = palette_[Mode3Colour0(c)]; - mode3_pixels[1] = palette_[Mode3Colour1(c)]; - } - break; - } + case 3: + for(size_t c = 0; c < 256; c++) { + // Prepare mode 3. + uint8_t *const mode3_pixels = reinterpret_cast(&mode3_output_[c]); + mode3_pixels[0] = palette_[Mode3Colour0(c)]; + mode3_pixels[1] = palette_[Mode3Colour1(c)]; + } + break; } + } - void patch_mode_table(size_t pen) { - switch(mode_) { - case 0: { - for(uint8_t c : mode0_palette_hits_[pen]) { - assert(c < mode0_output_.size()); - uint8_t *const mode0_pixels = reinterpret_cast(&mode0_output_[c]); - mode0_pixels[0] = palette_[Mode0Colour0(c)]; - mode0_pixels[1] = palette_[Mode0Colour1(c)]; - } - } break; - case 1: - if(pen >= mode1_palette_hits_.size()) return; - for(uint8_t c : mode1_palette_hits_[pen]) { - assert(c < mode1_output_.size()); - uint8_t *const mode1_pixels = reinterpret_cast(&mode1_output_[c]); - mode1_pixels[0] = palette_[Mode1Colour0(c)]; - mode1_pixels[1] = palette_[Mode1Colour1(c)]; - mode1_pixels[2] = palette_[Mode1Colour2(c)]; - mode1_pixels[3] = palette_[Mode1Colour3(c)]; - } - break; - case 2: - if(pen > 1) return; - // Whichever pen this is, there's only one table entry it doesn't touch, so just - // rebuild the whole thing. - build_mode_table(); - break; - case 3: - if(pen >= mode3_palette_hits_.size()) return; - // Same argument applies here as to case 1, as the unused bits aren't masked out. - for(uint8_t c : mode3_palette_hits_[pen]) { - assert(c < mode3_output_.size()); - uint8_t *const mode3_pixels = reinterpret_cast(&mode3_output_[c]); - mode3_pixels[0] = palette_[Mode3Colour0(c)]; - mode3_pixels[1] = palette_[Mode3Colour1(c)]; - } - break; - } + void patch_mode_table(size_t pen) { + switch(mode_) { + case 0: { + for(uint8_t c : mode0_palette_hits_[pen]) { + assert(c < mode0_output_.size()); + uint8_t *const mode0_pixels = reinterpret_cast(&mode0_output_[c]); + mode0_pixels[0] = palette_[Mode0Colour0(c)]; + mode0_pixels[1] = palette_[Mode0Colour1(c)]; + } + } break; + case 1: + if(pen >= mode1_palette_hits_.size()) return; + for(uint8_t c : mode1_palette_hits_[pen]) { + assert(c < mode1_output_.size()); + uint8_t *const mode1_pixels = reinterpret_cast(&mode1_output_[c]); + mode1_pixels[0] = palette_[Mode1Colour0(c)]; + mode1_pixels[1] = palette_[Mode1Colour1(c)]; + mode1_pixels[2] = palette_[Mode1Colour2(c)]; + mode1_pixels[3] = palette_[Mode1Colour3(c)]; + } + break; + case 2: + if(pen > 1) return; + // Whichever pen this is, there's only one table entry it doesn't touch, so just + // rebuild the whole thing. + build_mode_table(); + break; + case 3: + if(pen >= mode3_palette_hits_.size()) return; + // Same argument applies here as to case 1, as the unused bits aren't masked out. + for(uint8_t c : mode3_palette_hits_[pen]) { + assert(c < mode3_output_.size()); + uint8_t *const mode3_pixels = reinterpret_cast(&mode3_output_[c]); + mode3_pixels[0] = palette_[Mode3Colour0(c)]; + mode3_pixels[1] = palette_[Mode3Colour1(c)]; + } + break; } + } #undef Mode0Colour0 #undef Mode0Colour1 @@ -522,56 +522,56 @@ class CRTCBusHandler { #undef Mode3Colour0 #undef Mode3Colour1 - uint8_t mapped_palette_value(uint8_t colour) { + uint8_t mapped_palette_value(uint8_t colour) { #define COL(r, g, b) (r << 4) | (g << 2) | b - constexpr uint8_t mapping[32] = { - COL(1, 1, 1), COL(1, 1, 1), COL(0, 2, 1), COL(2, 2, 1), - COL(0, 0, 1), COL(2, 0, 1), COL(0, 1, 1), COL(2, 1, 1), - COL(2, 0, 1), COL(2, 2, 1), COL(2, 2, 0), COL(2, 2, 2), - COL(2, 0, 0), COL(2, 0, 2), COL(2, 1, 0), COL(2, 1, 2), - COL(0, 0, 1), COL(0, 2, 1), COL(0, 2, 0), COL(0, 2, 2), - COL(0, 0, 0), COL(0, 0, 2), COL(0, 1, 0), COL(0, 1, 2), - COL(1, 0, 1), COL(1, 2, 1), COL(1, 2, 0), COL(1, 2, 2), - COL(1, 0, 0), COL(1, 0, 2), COL(1, 1, 0), COL(1, 1, 2), - }; + constexpr uint8_t mapping[32] = { + COL(1, 1, 1), COL(1, 1, 1), COL(0, 2, 1), COL(2, 2, 1), + COL(0, 0, 1), COL(2, 0, 1), COL(0, 1, 1), COL(2, 1, 1), + COL(2, 0, 1), COL(2, 2, 1), COL(2, 2, 0), COL(2, 2, 2), + COL(2, 0, 0), COL(2, 0, 2), COL(2, 1, 0), COL(2, 1, 2), + COL(0, 0, 1), COL(0, 2, 1), COL(0, 2, 0), COL(0, 2, 2), + COL(0, 0, 0), COL(0, 0, 2), COL(0, 1, 0), COL(0, 1, 2), + COL(1, 0, 1), COL(1, 2, 1), COL(1, 2, 0), COL(1, 2, 2), + COL(1, 0, 0), COL(1, 0, 2), COL(1, 1, 0), COL(1, 1, 2), + }; #undef COL - return mapping[colour]; - } + return mapping[colour]; + } - enum class OutputMode { - Sync, - Blank, - ColourBurst, - Border, - Pixels - } previous_output_mode_ = OutputMode::Sync; - int cycles_ = 0; + enum class OutputMode { + Sync, + Blank, + ColourBurst, + Border, + Pixels + } previous_output_mode_ = OutputMode::Sync; + int cycles_ = 0; - bool was_hsync_ = false, was_vsync_ = false; - int cycles_into_hsync_ = 0; + bool was_hsync_ = false, was_vsync_ = false; + int cycles_into_hsync_ = 0; - Outputs::CRT::CRT crt_; - uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; + Outputs::CRT::CRT crt_; + uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr; - const uint8_t *const ram_ = nullptr; + const uint8_t *const ram_ = nullptr; - int next_mode_ = 2, mode_ = 2; + int next_mode_ = 2, mode_ = 2; - int pixel_divider_ = 1; - std::array mode0_output_; - std::array mode1_output_; - std::array mode2_output_; - std::array mode3_output_; + int pixel_divider_ = 1; + std::array mode0_output_; + std::array mode1_output_; + std::array mode2_output_; + std::array mode3_output_; - std::array, 16> mode0_palette_hits_; - std::array, 4> mode1_palette_hits_; - std::array, 4> mode3_palette_hits_; + std::array, 16> mode0_palette_hits_; + std::array, 4> mode1_palette_hits_; + std::array, 4> mode3_palette_hits_; - int pen_ = 0; - uint8_t palette_[16]; - uint8_t border_ = 0; + int pen_ = 0; + uint8_t palette_[16]; + uint8_t border_ = 0; - InterruptTimer &interrupt_timer_; + InterruptTimer &interrupt_timer_; }; using CRTC = Motorola::CRTC::CRTC6845< CRTCBusHandler, @@ -583,160 +583,160 @@ using CRTC = Motorola::CRTC::CRTC6845< Also owns the joysticks. */ class KeyboardState: public GI::AY38910::PortHandler { +public: + KeyboardState() { + joysticks_.emplace_back(new Joystick(rows_[9])); + joysticks_.emplace_back(new Joystick(joy2_state_)); + } + + /*! + Sets the row currently being reported to the AY. + */ + void set_row(int row) { + row_ = size_t(row); + } + + /*! + Reports the state of the currently-selected row as Port A to the AY. + */ + uint8_t get_port_input(bool port_b) { + if(!port_b && row_ < sizeof(rows_)) { + return (row_ == 6) ? rows_[row_] & joy2_state_ : rows_[row_]; + } + + return 0xff; + } + + /*! + Sets whether @c key on line @c line is currently pressed. + */ + void set_is_pressed(bool is_pressed, int line, int key) { + int mask = 1 << key; + assert(size_t(line) < sizeof(rows_)); + if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask; + } + + /*! + Sets all keys as currently unpressed. + */ + void clear_all_keys() { + memset(rows_, 0xff, sizeof(rows_)); + } + + const std::vector> &get_joysticks() { + return joysticks_; + } + +private: + uint8_t joy2_state_ = 0xff; + uint8_t rows_[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + size_t row_ = 0; + std::vector> joysticks_; + + class Joystick: public Inputs::ConcreteJoystick { public: - KeyboardState() { - joysticks_.emplace_back(new Joystick(rows_[9])); - joysticks_.emplace_back(new Joystick(joy2_state_)); - } + Joystick(uint8_t &state) : + ConcreteJoystick({ + Input(Input::Up), + Input(Input::Down), + Input(Input::Left), + Input(Input::Right), + Input(Input::Fire, 0), + Input(Input::Fire, 1), + }), + state_(state) {} - /*! - Sets the row currently being reported to the AY. - */ - void set_row(int row) { - row_ = size_t(row); - } - - /*! - Reports the state of the currently-selected row as Port A to the AY. - */ - uint8_t get_port_input(bool port_b) { - if(!port_b && row_ < sizeof(rows_)) { - return (row_ == 6) ? rows_[row_] & joy2_state_ : rows_[row_]; + void did_set_input(const Input &input, bool is_active) final { + uint8_t mask = 0; + switch(input.type) { + default: return; + case Input::Up: mask = 0x01; break; + case Input::Down: mask = 0x02; break; + case Input::Left: mask = 0x04; break; + case Input::Right: mask = 0x08; break; + case Input::Fire: + if(input.info.control.index >= 2) return; + mask = input.info.control.index ? 0x20 : 0x10; + break; } - return 0xff; - } - - /*! - Sets whether @c key on line @c line is currently pressed. - */ - void set_is_pressed(bool is_pressed, int line, int key) { - int mask = 1 << key; - assert(size_t(line) < sizeof(rows_)); - if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask; - } - - /*! - Sets all keys as currently unpressed. - */ - void clear_all_keys() { - memset(rows_, 0xff, sizeof(rows_)); - } - - const std::vector> &get_joysticks() { - return joysticks_; + if(is_active) state_ &= ~mask; else state_ |= mask; } private: - uint8_t joy2_state_ = 0xff; - uint8_t rows_[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - size_t row_ = 0; - std::vector> joysticks_; - - class Joystick: public Inputs::ConcreteJoystick { - public: - Joystick(uint8_t &state) : - ConcreteJoystick({ - Input(Input::Up), - Input(Input::Down), - Input(Input::Left), - Input(Input::Right), - Input(Input::Fire, 0), - Input(Input::Fire, 1), - }), - state_(state) {} - - void did_set_input(const Input &input, bool is_active) final { - uint8_t mask = 0; - switch(input.type) { - default: return; - case Input::Up: mask = 0x01; break; - case Input::Down: mask = 0x02; break; - case Input::Left: mask = 0x04; break; - case Input::Right: mask = 0x08; break; - case Input::Fire: - if(input.info.control.index >= 2) return; - mask = input.info.control.index ? 0x20 : 0x10; - break; - } - - if(is_active) state_ &= ~mask; else state_ |= mask; - } - - private: - uint8_t &state_; - }; + uint8_t &state_; + }; }; /*! Provides the mechanism of receipt for input and output of the 8255's various ports. */ class i8255PortHandler : public Intel::i8255::PortHandler { - public: - i8255PortHandler( - KeyboardState &key_state, - const CRTC &crtc, - AYDeferrer &ay, - Storage::Tape::BinaryTapePlayer &tape_player) : - ay_(ay), - crtc_(crtc), - key_state_(key_state), - tape_player_(tape_player) {} +public: + i8255PortHandler( + KeyboardState &key_state, + const CRTC &crtc, + AYDeferrer &ay, + Storage::Tape::BinaryTapePlayer &tape_player) : + ay_(ay), + crtc_(crtc), + key_state_(key_state), + tape_player_(tape_player) {} - /// The i8255 will call this to set a new output value of @c value for @c port. - void set_value(int port, uint8_t value) { - switch(port) { - case 0: - // Port A is connected to the AY's data bus. - ay_.update(); - ay_.ay().set_data_input(value); - break; - case 1: - // Port B is an input only. So output goes nowehere. - break; - case 2: { - // The low four bits of the value sent to Port C select a keyboard line. - int key_row = value & 15; - key_state_.set_row(key_row); + /// The i8255 will call this to set a new output value of @c value for @c port. + void set_value(int port, uint8_t value) { + switch(port) { + case 0: + // Port A is connected to the AY's data bus. + ay_.update(); + ay_.ay().set_data_input(value); + break; + case 1: + // Port B is an input only. So output goes nowehere. + break; + case 2: { + // The low four bits of the value sent to Port C select a keyboard line. + int key_row = value & 15; + key_state_.set_row(key_row); - // Bit 4 sets the tape motor on or off. - tape_player_.set_motor_control((value & 0x10) ? true : false); - // Bit 5 sets the current tape output level - tape_player_.set_tape_output((value & 0x20) ? true : false); + // Bit 4 sets the tape motor on or off. + tape_player_.set_motor_control((value & 0x10) ? true : false); + // Bit 5 sets the current tape output level + tape_player_.set_tape_output((value & 0x20) ? true : false); - // Bits 6 and 7 set BDIR and BC1 for the AY. - ay_.ay().set_control_lines( - (GI::AY38910::ControlLines)( - ((value & 0x80) ? GI::AY38910::BDIR : 0) | - ((value & 0x40) ? GI::AY38910::BC1 : 0) | - GI::AY38910::BC2 - )); - } break; - } + // Bits 6 and 7 set BDIR and BC1 for the AY. + ay_.ay().set_control_lines( + (GI::AY38910::ControlLines)( + ((value & 0x80) ? GI::AY38910::BDIR : 0) | + ((value & 0x40) ? GI::AY38910::BC1 : 0) | + GI::AY38910::BC2 + )); + } break; } + } - /// The i8255 will call this to obtain a new input for @c port. - uint8_t get_value(int port) { - switch(port) { - case 0: return ay_.ay().get_data_output(); // Port A is wired to the AY - case 1: return - (crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync. - (tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input. - 0x7e; // Bits unimplemented: - // - // Bit 6: printer ready (1 = not) - // Bit 5: the expansion port /EXP pin, so depends on connected hardware - // Bit 4: 50/60Hz switch (1 = 50Hz) - // Bits 1-3: distributor ID (111 = Amstrad) - default: return 0xff; - } + /// The i8255 will call this to obtain a new input for @c port. + uint8_t get_value(int port) { + switch(port) { + case 0: return ay_.ay().get_data_output(); // Port A is wired to the AY + case 1: return + (crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync. + (tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input. + 0x7e; // Bits unimplemented: + // + // Bit 6: printer ready (1 = not) + // Bit 5: the expansion port /EXP pin, so depends on connected hardware + // Bit 4: 50/60Hz switch (1 = 50Hz) + // Bits 1-3: distributor ID (111 = Amstrad) + default: return 0xff; } + } - private: - AYDeferrer &ay_; - const CRTC &crtc_; - KeyboardState &key_state_; - Storage::Tape::BinaryTapePlayer &tape_player_; +private: + AYDeferrer &ay_; + const CRTC &crtc_; + KeyboardState &key_state_; + Storage::Tape::BinaryTapePlayer &tape_player_; }; /*! @@ -756,565 +756,565 @@ class ConcreteMachine: public Configurable::Device, public Machine, public Activity::Source { - public: - ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - z80_(*this), - crtc_bus_handler_(ram_, interrupt_timer_), - crtc_(crtc_bus_handler_), - i8255_port_handler_(key_state_, crtc_, ay_, tape_player_), - i8255_(i8255_port_handler_), - tape_player_(8000000), - crtc_counter_(HalfCycles(4)) // This starts the CRTC exactly out of phase with the CPU's memory accesses - { - // primary clock is 4Mhz - set_clock_rate(4000000); +public: + ConcreteMachine(const Analyser::Static::AmstradCPC::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + z80_(*this), + crtc_bus_handler_(ram_, interrupt_timer_), + crtc_(crtc_bus_handler_), + i8255_port_handler_(key_state_, crtc_, ay_, tape_player_), + i8255_(i8255_port_handler_), + tape_player_(8000000), + crtc_counter_(HalfCycles(4)) // This starts the CRTC exactly out of phase with the CPU's memory accesses + { + // primary clock is 4Mhz + set_clock_rate(4000000); - // ensure memory starts in a random state - Memory::Fuzz(ram_, sizeof(ram_)); + // ensure memory starts in a random state + Memory::Fuzz(ram_, sizeof(ram_)); - // register this class as the sleep observer for the FDC and tape - fdc_.set_clocking_hint_observer(this); - tape_player_.set_clocking_hint_observer(this); + // register this class as the sleep observer for the FDC and tape + fdc_.set_clocking_hint_observer(this); + tape_player_.set_clocking_hint_observer(this); - // install the keyboard state class as the AY port handler - ay_.ay().set_port_handler(&key_state_); + // install the keyboard state class as the AY port handler + ay_.ay().set_port_handler(&key_state_); - // construct the list of necessary ROMs - bool has_amsdos = false; - ROM::Name firmware, basic; + // construct the list of necessary ROMs + bool has_amsdos = false; + ROM::Name firmware, basic; - using Model = Analyser::Static::AmstradCPC::Target::Model; - switch(target.model) { - case Model::CPC464: - firmware = ROM::Name::CPC464Firmware; - basic = ROM::Name::CPC464BASIC; - break; - case Model::CPC664: - firmware = ROM::Name::CPC664Firmware; - basic = ROM::Name::CPC664BASIC; - has_amsdos = true; - break; - default: - firmware = ROM::Name::CPC6128Firmware; - basic = ROM::Name::CPC6128BASIC; - has_amsdos = true; - break; - } - - ROM::Request request = ROM::Request(firmware) && ROM::Request(basic); - if(has_amsdos) { - request = request && ROM::Request(ROM::Name::AMSDOS); - } - - // Fetch and verify the ROMs. - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - - if(has_amsdos) { - roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second; - } - roms_[ROMType::OS] = roms.find(firmware)->second; - roms_[ROMType::BASIC] = roms.find(basic)->second; - - // Establish default memory map - upper_rom_is_paged_ = true; - upper_rom_ = ROMType::BASIC; - - write_pointers_[0] = &ram_[0x0000]; - write_pointers_[1] = &ram_[0x4000]; - write_pointers_[2] = &ram_[0x8000]; - write_pointers_[3] = &ram_[0xc000]; - - read_pointers_[0] = roms_[ROMType::OS].data(); - read_pointers_[1] = write_pointers_[1]; - read_pointers_[2] = write_pointers_[2]; - read_pointers_[3] = roms_[upper_rom_].data(); - - // Set total RAM available. - has_128k_ = target.model == Model::CPC6128; - - // Type whatever is required. - if(!target.loading_command.empty()) { - type_string(target.loading_command); - } - - insert_media(target.media); + using Model = Analyser::Static::AmstradCPC::Target::Model; + switch(target.model) { + case Model::CPC464: + firmware = ROM::Name::CPC464Firmware; + basic = ROM::Name::CPC464BASIC; + break; + case Model::CPC664: + firmware = ROM::Name::CPC664Firmware; + basic = ROM::Name::CPC664BASIC; + has_amsdos = true; + break; + default: + firmware = ROM::Name::CPC6128Firmware; + basic = ROM::Name::CPC6128BASIC; + has_amsdos = true; + break; } - /// The entry point for performing a partial Z80 machine cycle. - forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - // Amstrad CPC timing scheme: assert WAIT for three out of four cycles. - clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7); - z80_.set_wait_line(clock_offset_ >= HalfCycles(2)); + ROM::Request request = ROM::Request(firmware) && ROM::Request(basic); + if(has_amsdos) { + request = request && ROM::Request(ROM::Name::AMSDOS); + } - // Float this out as a lambda to allow easy repositioning relative to the CPU activity; - // for now this is largely experimental. - const auto update_subsystems = [&] { - // Update the CRTC once every eight half cycles; aiming for half-cycle 4 as - // per the initial seed to the crtc_counter_, but any time in the final four - // will do as it's safe to conclude that nobody else has touched video RAM - // during that whole window. - crtc_counter_ += cycle.length; - const Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); - if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); + // Fetch and verify the ROMs. + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } - // Check whether that prompted a change in the interrupt line. If so then date - // it to whenever the cycle was triggered. - if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_); + if(has_amsdos) { + roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second; + } + roms_[ROMType::OS] = roms.find(firmware)->second; + roms_[ROMType::BASIC] = roms.find(basic)->second; - // TODO (in the player, not here): adapt it to accept an input clock rate and - // run_for as HalfCycles. - if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral()); + // Establish default memory map + upper_rom_is_paged_ = true; + upper_rom_ = ROMType::BASIC; - // Pump the AY. - ay_.run_for(cycle.length); + write_pointers_[0] = &ram_[0x0000]; + write_pointers_[1] = &ram_[0x4000]; + write_pointers_[2] = &ram_[0x8000]; + write_pointers_[3] = &ram_[0xc000]; - if constexpr (has_fdc) { - // Clock the FDC, if connected, using a lazy scale by two. - time_since_fdc_update_ += cycle.length; - } + read_pointers_[0] = roms_[ROMType::OS].data(); + read_pointers_[1] = write_pointers_[1]; + read_pointers_[2] = write_pointers_[2]; + read_pointers_[3] = roms_[upper_rom_].data(); - // Update typing activity. - if(typer_) typer_->run_for(cycle.length); - }; + // Set total RAM available. + has_128k_ = target.model == Model::CPC6128; - // Continue only if action strictly required. - if(cycle.is_terminal()) { - uint16_t address = cycle.address ? *cycle.address : 0x0000; - switch(cycle.operation) { - case CPU::Z80::PartialMachineCycle::ReadOpcode: + // Type whatever is required. + if(!target.loading_command.empty()) { + type_string(target.loading_command); + } - // TODO: just capturing byte reads as below doesn't seem to do that much in terms of acceleration; - // I'm not immediately clear whether that's just because the machine still has to sit through - // pilot tone in real time, or just that almost no software uses the ROM loader. - if(use_fast_tape_hack_ && address == tape_read_byte_address && read_pointers_[0] == roms_[ROMType::OS].data()) { - using Parser = Storage::Tape::ZXSpectrum::Parser; - Parser parser(Parser::MachineType::AmstradCPC); + insert_media(target.media); + } - const auto speed = read_pointers_[tape_speed_value_address >> 14][tape_speed_value_address & 16383]; - parser.set_cpc_read_speed(speed); + /// The entry point for performing a partial Z80 machine cycle. + forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + // Amstrad CPC timing scheme: assert WAIT for three out of four cycles. + clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7); + z80_.set_wait_line(clock_offset_ >= HalfCycles(2)); - // Seed with the current pulse; the CPC will have finished the - // preceding symbol and be a short way into the pulse that should determine the - // first bit of this byte. - parser.process_pulse(tape_player_.get_current_pulse()); - const auto byte = parser.get_byte(tape_player_.get_tape()); - auto flags = z80_.value_of(CPU::Z80::Register::Flags); + // Float this out as a lambda to allow easy repositioning relative to the CPU activity; + // for now this is largely experimental. + const auto update_subsystems = [&] { + // Update the CRTC once every eight half cycles; aiming for half-cycle 4 as + // per the initial seed to the crtc_counter_, but any time in the final four + // will do as it's safe to conclude that nobody else has touched video RAM + // during that whole window. + crtc_counter_ += cycle.length; + const Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); + if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); - if(byte) { - // In A ROM-esque fashion, begin the first pulse after the final one - // that was just consumed. - tape_player_.complete_pulse(); + // Check whether that prompted a change in the interrupt line. If so then date + // it to whenever the cycle was triggered. + if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_); - // Update in-memory CRC. - auto crc_value = - uint16_t( - read_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] | - (read_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] << 8) - ); + // TODO (in the player, not here): adapt it to accept an input clock rate and + // run_for as HalfCycles. + if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral()); - tape_crc_.set_value(crc_value); - tape_crc_.add(*byte); - crc_value = tape_crc_.get_value(); + // Pump the AY. + ay_.run_for(cycle.length); - write_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] = uint8_t(crc_value); - write_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] = uint8_t(crc_value >> 8); + if constexpr (has_fdc) { + // Clock the FDC, if connected, using a lazy scale by two. + time_since_fdc_update_ += cycle.length; + } - // Indicate successful byte read. - z80_.set_value_of(CPU::Z80::Register::A, *byte); - flags |= CPU::Z80::Flag::Carry; - } else { - // TODO: return tape player to previous state and decline to serve. - z80_.set_value_of(CPU::Z80::Register::A, 0); - flags &= ~CPU::Z80::Flag::Carry; - } - z80_.set_value_of(CPU::Z80::Register::Flags, flags); + // Update typing activity. + if(typer_) typer_->run_for(cycle.length); + }; - // RET. - *cycle.value = 0xc9; - break; + // Continue only if action strictly required. + if(cycle.is_terminal()) { + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: + + // TODO: just capturing byte reads as below doesn't seem to do that much in terms of acceleration; + // I'm not immediately clear whether that's just because the machine still has to sit through + // pilot tone in real time, or just that almost no software uses the ROM loader. + if(use_fast_tape_hack_ && address == tape_read_byte_address && read_pointers_[0] == roms_[ROMType::OS].data()) { + using Parser = Storage::Tape::ZXSpectrum::Parser; + Parser parser(Parser::MachineType::AmstradCPC); + + const auto speed = read_pointers_[tape_speed_value_address >> 14][tape_speed_value_address & 16383]; + parser.set_cpc_read_speed(speed); + + // Seed with the current pulse; the CPC will have finished the + // preceding symbol and be a short way into the pulse that should determine the + // first bit of this byte. + parser.process_pulse(tape_player_.get_current_pulse()); + const auto byte = parser.get_byte(tape_player_.get_tape()); + auto flags = z80_.value_of(CPU::Z80::Register::Flags); + + if(byte) { + // In A ROM-esque fashion, begin the first pulse after the final one + // that was just consumed. + tape_player_.complete_pulse(); + + // Update in-memory CRC. + auto crc_value = + uint16_t( + read_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] | + (read_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] << 8) + ); + + tape_crc_.set_value(crc_value); + tape_crc_.add(*byte); + crc_value = tape_crc_.get_value(); + + write_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] = uint8_t(crc_value); + write_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] = uint8_t(crc_value >> 8); + + // Indicate successful byte read. + z80_.set_value_of(CPU::Z80::Register::A, *byte); + flags |= CPU::Z80::Flag::Carry; + } else { + // TODO: return tape player to previous state and decline to serve. + z80_.set_value_of(CPU::Z80::Register::A, 0); + flags &= ~CPU::Z80::Flag::Carry; } + z80_.set_value_of(CPU::Z80::Register::Flags, flags); - if constexpr (catches_ssm) { - ssm_code_ = (ssm_code_ << 8) | read_pointers_[address >> 14][address & 16383]; - if(ssm_delegate_) { - if((ssm_code_ & 0xff00ff00) == 0xed00ed00) { - const auto code = uint16_t( - ((ssm_code_ << 8) & 0xff00) | ((ssm_code_ >> 16) & 0x00ff) - ); + // RET. + *cycle.value = 0xc9; + break; + } - const auto is_valid = [](uint8_t digit) { - return - (digit <= 0x3f) || - (digit >= 0x7f && digit <= 0x9f) || - (digit >= 0xa4 && digit <= 0xa7) || - (digit >= 0xac && digit <= 0xaf) || - (digit >= 0xb4 && digit <= 0xb7) || - (digit >= 0xbc && digit <= 0xbf) || - (digit >= 0xc0 && digit <= 0xfd); - }; + if constexpr (catches_ssm) { + ssm_code_ = (ssm_code_ << 8) | read_pointers_[address >> 14][address & 16383]; + if(ssm_delegate_) { + if((ssm_code_ & 0xff00ff00) == 0xed00ed00) { + const auto code = uint16_t( + ((ssm_code_ << 8) & 0xff00) | ((ssm_code_ >> 16) & 0x00ff) + ); - if( - is_valid(static_cast(code)) && is_valid(static_cast(code >> 8)) - ) { - ssm_delegate_->perform(code); - ssm_code_ = 0; - } - } else if((ssm_code_ & 0xffff) == 0xedfe) { - ssm_delegate_->perform(0xfffe); - } else if((ssm_code_ & 0xffff) == 0xedff) { - ssm_delegate_->perform(0xffff); + const auto is_valid = [](uint8_t digit) { + return + (digit <= 0x3f) || + (digit >= 0x7f && digit <= 0x9f) || + (digit >= 0xa4 && digit <= 0xa7) || + (digit >= 0xac && digit <= 0xaf) || + (digit >= 0xb4 && digit <= 0xb7) || + (digit >= 0xbc && digit <= 0xbf) || + (digit >= 0xc0 && digit <= 0xfd); + }; + + if( + is_valid(static_cast(code)) && is_valid(static_cast(code >> 8)) + ) { + ssm_delegate_->perform(code); + ssm_code_ = 0; } + } else if((ssm_code_ & 0xffff) == 0xedfe) { + ssm_delegate_->perform(0xfffe); + } else if((ssm_code_ & 0xffff) == 0xedff) { + ssm_delegate_->perform(0xffff); } } - [[fallthrough]]; + } + [[fallthrough]]; - case CPU::Z80::PartialMachineCycle::Read: - *cycle.value = read_pointers_[address >> 14][address & 16383]; - break; + case CPU::Z80::PartialMachineCycle::Read: + *cycle.value = read_pointers_[address >> 14][address & 16383]; + break; - case CPU::Z80::PartialMachineCycle::Write: - write_pointers_[address >> 14][address & 16383] = *cycle.value; - break; + case CPU::Z80::PartialMachineCycle::Write: + write_pointers_[address >> 14][address & 16383] = *cycle.value; + break; - case CPU::Z80::PartialMachineCycle::Output: - // Check for a gate array access. - if((address & 0xc000) == 0x4000) { - write_to_gate_array(*cycle.value); + case CPU::Z80::PartialMachineCycle::Output: + // Check for a gate array access. + if((address & 0xc000) == 0x4000) { + write_to_gate_array(*cycle.value); + } + + // Check for an upper ROM selection + if constexpr (has_fdc) { + if(!(address&0x2000)) { + upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC; + if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); } + } - // Check for an upper ROM selection - if constexpr (has_fdc) { - if(!(address&0x2000)) { - upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC; - if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data(); - } + // Check for a CRTC access + if(!(address & 0x4000)) { + switch((address >> 8) & 3) { + case 0: crtc_.select_register(*cycle.value); break; + case 1: crtc_.set_register(*cycle.value); break; + default: break; } + } - // Check for a CRTC access - if(!(address & 0x4000)) { - switch((address >> 8) & 3) { - case 0: crtc_.select_register(*cycle.value); break; - case 1: crtc_.set_register(*cycle.value); break; - default: break; - } - } - - // Check for an 8255 PIO access - if(!(address & 0x800)) { - i8255_.write((address >> 8) & 3, *cycle.value); - } - - if constexpr (has_fdc) { - // Check for an FDC access - if((address & 0x580) == 0x100) { - flush_fdc(); - fdc_.write(address & 1, *cycle.value); - } - - // Check for a disk motor access - if(!(address & 0x580)) { - flush_fdc(); - fdc_.set_motor_on(!!(*cycle.value)); - } - } - break; - - case CPU::Z80::PartialMachineCycle::Input: - // Default to nothing answering - *cycle.value = 0xff; - - // Check for a PIO access - if(!(address & 0x800)) { - *cycle.value &= i8255_.read((address >> 8) & 3); - } + // Check for an 8255 PIO access + if(!(address & 0x800)) { + i8255_.write((address >> 8) & 3, *cycle.value); + } + if constexpr (has_fdc) { // Check for an FDC access - if constexpr (has_fdc) { - if((address & 0x580) == 0x100) { - flush_fdc(); - *cycle.value &= fdc_.read(address & 1); - } + if((address & 0x580) == 0x100) { + flush_fdc(); + fdc_.write(address & 1, *cycle.value); } - // Check for a CRTC access; the below is not a typo, the CRTC can be selected - // for writing via an input, and will sample whatever happens to be available - if(!(address & 0x4000)) { - switch((address >> 8) & 3) { - case 0: crtc_.select_register(*cycle.value); break; - case 1: crtc_.set_register(*cycle.value); break; - case 2: *cycle.value &= crtc_.get_status(); break; - case 3: *cycle.value &= crtc_.get_register(); break; - } + // Check for a disk motor access + if(!(address & 0x580)) { + flush_fdc(); + fdc_.set_motor_on(!!(*cycle.value)); } + } + break; - // As with the CRTC, the gate array will sample the bus if the address decoding - // implies that it should, unaware of data direction - if((address & 0xc000) == 0x4000) { - write_to_gate_array(*cycle.value); + case CPU::Z80::PartialMachineCycle::Input: + // Default to nothing answering + *cycle.value = 0xff; + + // Check for a PIO access + if(!(address & 0x800)) { + *cycle.value &= i8255_.read((address >> 8) & 3); + } + + // Check for an FDC access + if constexpr (has_fdc) { + if((address & 0x580) == 0x100) { + flush_fdc(); + *cycle.value &= fdc_.read(address & 1); } - break; + } - case CPU::Z80::PartialMachineCycle::Interrupt: - // Nothing is loaded onto the bus during an interrupt acknowledge, but - // the fact of the acknowledge needs to be posted on to the interrupt timer. - *cycle.value = 0xff; - interrupt_timer_.signal_interrupt_acknowledge(); - break; + // Check for a CRTC access; the below is not a typo, the CRTC can be selected + // for writing via an input, and will sample whatever happens to be available + if(!(address & 0x4000)) { + switch((address >> 8) & 3) { + case 0: crtc_.select_register(*cycle.value); break; + case 1: crtc_.set_register(*cycle.value); break; + case 2: *cycle.value &= crtc_.get_status(); break; + case 3: *cycle.value &= crtc_.get_register(); break; + } + } - default: break; - } + // As with the CRTC, the gate array will sample the bus if the address decoding + // implies that it should, unaware of data direction + if((address & 0xc000) == 0x4000) { + write_to_gate_array(*cycle.value); + } + break; - // Check whether the interrupt signal has changed due to CPU intervention. - if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request()); + case CPU::Z80::PartialMachineCycle::Interrupt: + // Nothing is loaded onto the bus during an interrupt acknowledge, but + // the fact of the acknowledge needs to be posted on to the interrupt timer. + *cycle.value = 0xff; + interrupt_timer_.signal_interrupt_acknowledge(); + break; + + default: break; } - update_subsystems(); - - // This implementation doesn't use time-stuffing; once in-phase waits won't be longer - // than a single cycle so there's no real performance benefit to trying to find the - // next non-wait when a wait cycle comes in, and there'd be no benefit to reproducing - // the Z80's knowledge of where wait cycles occur here. - return HalfCycles(0); + // Check whether the interrupt signal has changed due to CPU intervention. + if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request()); } - /// Fields requests to pump all output. - void flush_output(int outputs) final { - // Just flush the AY. - if(outputs & Output::Audio) { - ay_.update(); - ay_.flush(); - } + update_subsystems(); - // Always flush the FDC. - flush_fdc(); + // This implementation doesn't use time-stuffing; once in-phase waits won't be longer + // than a single cycle so there's no real performance benefit to trying to find the + // next non-wait when a wait cycle comes in, and there'd be no benefit to reproducing + // the Z80's knowledge of where wait cycles occur here. + return HalfCycles(0); + } + + /// Fields requests to pump all output. + void flush_output(int outputs) final { + // Just flush the AY. + if(outputs & Output::Audio) { + ay_.update(); + ay_.flush(); } - /// A CRTMachine function; sets the destination for video. - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - crtc_bus_handler_.set_scan_target(scan_target); - } + // Always flush the FDC. + flush_fdc(); + } - /// A CRTMachine function; returns the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return crtc_bus_handler_.get_scaled_scan_status(); - } + /// A CRTMachine function; sets the destination for video. + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + crtc_bus_handler_.set_scan_target(scan_target); + } - /// A CRTMachine function; sets the output display type. - void set_display_type(Outputs::Display::DisplayType display_type) final { - crtc_bus_handler_.set_display_type(display_type); - } + /// A CRTMachine function; returns the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return crtc_bus_handler_.get_scaled_scan_status(); + } - /// A CRTMachine function; gets the output display type. - Outputs::Display::DisplayType get_display_type() const final { - return crtc_bus_handler_.get_display_type(); - } + /// A CRTMachine function; sets the output display type. + void set_display_type(Outputs::Display::DisplayType display_type) final { + crtc_bus_handler_.set_display_type(display_type); + } - /// @returns the speaker in use. - Outputs::Speaker::Speaker *get_speaker() final { - return ay_.get_speaker(); - } + /// A CRTMachine function; gets the output display type. + Outputs::Display::DisplayType get_display_type() const final { + return crtc_bus_handler_.get_display_type(); + } - /// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method. - void run_for(const Cycles cycles) final { - z80_.run_for(cycles); - } + /// @returns the speaker in use. + Outputs::Speaker::Speaker *get_speaker() final { + return ay_.get_speaker(); + } - bool insert_media(const Analyser::Static::Media &media) final { - // If there are any tapes supplied, use the first of them. - if(!media.tapes.empty()) { - tape_player_.set_tape(media.tapes.front()); - set_use_fast_tape_hack(); - } + /// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method. + void run_for(const Cycles cycles) final { + z80_.run_for(cycles); + } - // Insert up to four disks. - int c = 0; - for(auto &disk : media.disks) { - fdc_.set_disk(disk, c); - c++; - if(c == 4) break; - } - - return !media.tapes.empty() || (!media.disks.empty() && has_fdc); - } - - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { - fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None; - tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; - } - - void set_ssm_delegate(SSMDelegate *delegate) final { - ssm_delegate_ = delegate; - } - - // MARK: - Keyboard - void type_string(const std::string &string) final { - Utility::TypeRecipient::add_typer(string); - } - - bool can_type(char c) const final { - return Utility::TypeRecipient::can_type(c); - } - - HalfCycles get_typer_delay(const std::string &) const final { - return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0); - } - - HalfCycles get_typer_frequency() const final { - return Cycles(160'000); // Perform one key transition per frame and a half. - } - - // See header; sets a key as either pressed or released. - void set_key_state(uint16_t key, bool isPressed) final { - key_state_.set_is_pressed(isPressed, key >> 4, key & 7); - } - - // See header; sets all keys to released. - void clear_all_keys() final { - key_state_.clear_all_keys(); - } - - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; - } - - // MARK: - Activity Source - void set_activity_observer([[maybe_unused]] Activity::Observer *observer) final { - if constexpr (has_fdc) fdc_.set_activity_observer(observer); - tape_player_.set_activity_observer(observer); - } - - // MARK: - Configuration options. - std::unique_ptr get_options() const final { - auto options = std::make_unique(Configurable::OptionsType::UserFriendly); - options->output = get_video_signal_configurable(); - options->quickload = allow_fast_tape_hack_; - return options; - } - - void set_options(const std::unique_ptr &str) { - const auto options = dynamic_cast(str.get()); - set_video_signal_configurable(options->output); - allow_fast_tape_hack_ = options->quickload; + bool insert_media(const Analyser::Static::Media &media) final { + // If there are any tapes supplied, use the first of them. + if(!media.tapes.empty()) { + tape_player_.set_tape(media.tapes.front()); set_use_fast_tape_hack(); } - // MARK: - Joysticks - const std::vector> &get_joysticks() final { - return key_state_.get_joysticks(); + // Insert up to four disks. + int c = 0; + for(auto &disk : media.disks) { + fdc_.set_disk(disk, c); + c++; + if(c == 4) break; } - private: - inline void write_to_gate_array(uint8_t value) { - switch(value >> 6) { - case 0: crtc_bus_handler_.select_pen(value & 0x1f); break; - case 1: crtc_bus_handler_.set_colour(value & 0x1f); break; - case 2: - // Perform ROM paging. - read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[ROMType::OS].data(); + return !media.tapes.empty() || (!media.disks.empty() && has_fdc); + } - upper_rom_is_paged_ = !(value & 8); - read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { + fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None; + tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; + } - // Reset the interrupt timer if requested. - if(value & 0x10) interrupt_timer_.reset_count(); + void set_ssm_delegate(SSMDelegate *delegate) final { + ssm_delegate_ = delegate; + } - // Post the next mode. - crtc_bus_handler_.set_next_mode(value & 3); - break; - case 3: - // Perform RAM paging, if 128kb is permitted. - if(has_128k_) { - const bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0]; - const bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3]; + // MARK: - Keyboard + void type_string(const std::string &string) final { + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) const final { + return Utility::TypeRecipient::can_type(c); + } + + HalfCycles get_typer_delay(const std::string &) const final { + return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0); + } + + HalfCycles get_typer_frequency() const final { + return Cycles(160'000); // Perform one key transition per frame and a half. + } + + // See header; sets a key as either pressed or released. + void set_key_state(uint16_t key, bool isPressed) final { + key_state_.set_is_pressed(isPressed, key >> 4, key & 7); + } + + // See header; sets all keys to released. + void clear_all_keys() final { + key_state_.clear_all_keys(); + } + + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + + // MARK: - Activity Source + void set_activity_observer([[maybe_unused]] Activity::Observer *observer) final { + if constexpr (has_fdc) fdc_.set_activity_observer(observer); + tape_player_.set_activity_observer(observer); + } + + // MARK: - Configuration options. + std::unique_ptr get_options() const final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->quickload = allow_fast_tape_hack_; + return options; + } + + void set_options(const std::unique_ptr &str) { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); + allow_fast_tape_hack_ = options->quickload; + set_use_fast_tape_hack(); + } + + // MARK: - Joysticks + const std::vector> &get_joysticks() final { + return key_state_.get_joysticks(); + } + +private: + inline void write_to_gate_array(uint8_t value) { + switch(value >> 6) { + case 0: crtc_bus_handler_.select_pen(value & 0x1f); break; + case 1: crtc_bus_handler_.set_colour(value & 0x1f); break; + case 2: + // Perform ROM paging. + read_pointers_[0] = (value & 4) ? write_pointers_[0] : roms_[ROMType::OS].data(); + + upper_rom_is_paged_ = !(value & 8); + read_pointers_[3] = upper_rom_is_paged_ ? roms_[upper_rom_].data() : write_pointers_[3]; + + // Reset the interrupt timer if requested. + if(value & 0x10) interrupt_timer_.reset_count(); + + // Post the next mode. + crtc_bus_handler_.set_next_mode(value & 3); + break; + case 3: + // Perform RAM paging, if 128kb is permitted. + if(has_128k_) { + const bool adjust_low_read_pointer = read_pointers_[0] == write_pointers_[0]; + const bool adjust_high_read_pointer = read_pointers_[3] == write_pointers_[3]; #define RAM_BANK(x) &ram_[x * 16384] #define RAM_CONFIG(a, b, c, d) write_pointers_[0] = RAM_BANK(a); write_pointers_[1] = RAM_BANK(b); write_pointers_[2] = RAM_BANK(c); write_pointers_[3] = RAM_BANK(d); - switch(value & 7) { - case 0: RAM_CONFIG(0, 1, 2, 3); break; - case 1: RAM_CONFIG(0, 1, 2, 7); break; - case 2: RAM_CONFIG(4, 5, 6, 7); break; - case 3: RAM_CONFIG(0, 3, 2, 7); break; - case 4: RAM_CONFIG(0, 4, 2, 3); break; - case 5: RAM_CONFIG(0, 5, 2, 3); break; - case 6: RAM_CONFIG(0, 6, 2, 3); break; - case 7: RAM_CONFIG(0, 7, 2, 3); break; - } + switch(value & 7) { + case 0: RAM_CONFIG(0, 1, 2, 3); break; + case 1: RAM_CONFIG(0, 1, 2, 7); break; + case 2: RAM_CONFIG(4, 5, 6, 7); break; + case 3: RAM_CONFIG(0, 3, 2, 7); break; + case 4: RAM_CONFIG(0, 4, 2, 3); break; + case 5: RAM_CONFIG(0, 5, 2, 3); break; + case 6: RAM_CONFIG(0, 6, 2, 3); break; + case 7: RAM_CONFIG(0, 7, 2, 3); break; + } #undef RAM_CONFIG #undef RAM_BANK - if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0]; - read_pointers_[1] = write_pointers_[1]; - read_pointers_[2] = write_pointers_[2]; - if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3]; - } - break; - } - } - - CPU::Z80::Processor z80_; - - CRTCBusHandler crtc_bus_handler_; - CRTC crtc_; - - AYDeferrer ay_; - i8255PortHandler i8255_port_handler_; - Intel::i8255::i8255 i8255_; - - Amstrad::FDC fdc_; - HalfCycles time_since_fdc_update_; - void flush_fdc() { - if constexpr (has_fdc) { - // Clock the FDC, if connected, using a lazy scale by two - if(!fdc_is_sleeping_) { - fdc_.run_for(Cycles(time_since_fdc_update_.as_integral())); + if(adjust_low_read_pointer) read_pointers_[0] = write_pointers_[0]; + read_pointers_[1] = write_pointers_[1]; + read_pointers_[2] = write_pointers_[2]; + if(adjust_high_read_pointer) read_pointers_[3] = write_pointers_[3]; } - time_since_fdc_update_ = HalfCycles(0); + break; + } + } + + CPU::Z80::Processor z80_; + + CRTCBusHandler crtc_bus_handler_; + CRTC crtc_; + + AYDeferrer ay_; + i8255PortHandler i8255_port_handler_; + Intel::i8255::i8255 i8255_; + + Amstrad::FDC fdc_; + HalfCycles time_since_fdc_update_; + void flush_fdc() { + if constexpr (has_fdc) { + // Clock the FDC, if connected, using a lazy scale by two + if(!fdc_is_sleeping_) { + fdc_.run_for(Cycles(time_since_fdc_update_.as_integral())); } + time_since_fdc_update_ = HalfCycles(0); } + } - InterruptTimer interrupt_timer_; - Storage::Tape::BinaryTapePlayer tape_player_; + InterruptTimer interrupt_timer_; + Storage::Tape::BinaryTapePlayer tape_player_; - // By luck these values are the same between the 664 and the 6128; - // therefore the has_fdc template flag is sufficient to locate them. - static constexpr uint16_t tape_read_byte_address = has_fdc ? 0x2b20 : 0x29b0; - static constexpr uint16_t tape_speed_value_address = has_fdc ? 0xb1e7 : 0xbc8f; - static constexpr uint16_t tape_crc_address = has_fdc ? 0xb1eb : 0xb8d3; - CRC::CCITT tape_crc_; - bool use_fast_tape_hack_ = false; - bool allow_fast_tape_hack_ = false; - void set_use_fast_tape_hack() { - use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape(); - } + // By luck these values are the same between the 664 and the 6128; + // therefore the has_fdc template flag is sufficient to locate them. + static constexpr uint16_t tape_read_byte_address = has_fdc ? 0x2b20 : 0x29b0; + static constexpr uint16_t tape_speed_value_address = has_fdc ? 0xb1e7 : 0xbc8f; + static constexpr uint16_t tape_crc_address = has_fdc ? 0xb1eb : 0xb8d3; + CRC::CCITT tape_crc_; + bool use_fast_tape_hack_ = false; + bool allow_fast_tape_hack_ = false; + void set_use_fast_tape_hack() { + use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape(); + } - HalfCycles clock_offset_; - HalfCycles crtc_counter_; - HalfCycles half_cycles_since_ay_update_; + HalfCycles clock_offset_; + HalfCycles crtc_counter_; + HalfCycles half_cycles_since_ay_update_; - bool fdc_is_sleeping_ = false; - bool tape_player_is_sleeping_ = false; - bool has_128k_ = false; + bool fdc_is_sleeping_ = false; + bool tape_player_is_sleeping_ = false; + bool has_128k_ = false; - enum ROMType: int { - AMSDOS = 0, OS = 1, BASIC = 2 - }; - std::vector roms_[3]; - bool upper_rom_is_paged_ = false; - ROMType upper_rom_; + enum ROMType: int { + AMSDOS = 0, OS = 1, BASIC = 2 + }; + std::vector roms_[3]; + bool upper_rom_is_paged_ = false; + ROMType upper_rom_; - uint8_t *ram_pages_[4]{}; - const uint8_t *read_pointers_[4]{}; - uint8_t *write_pointers_[4]{}; + uint8_t *ram_pages_[4]{}; + const uint8_t *read_pointers_[4]{}; + uint8_t *write_pointers_[4]{}; - KeyboardState key_state_; - AmstradCPC::KeyboardMapper keyboard_mapper_; + KeyboardState key_state_; + AmstradCPC::KeyboardMapper keyboard_mapper_; - SSMDelegate *ssm_delegate_ = nullptr; - uint32_t ssm_code_ = 0; + SSMDelegate *ssm_delegate_ = nullptr; + uint32_t ssm_code_ = 0; - bool has_run_ = false; - uint8_t ram_[128 * 1024]; + bool has_run_ = false; + uint8_t ram_[128 * 1024]; }; } diff --git a/Machines/AmstradCPC/FDC.hpp b/Machines/AmstradCPC/FDC.hpp index 4cf2424c7..21389b686 100644 --- a/Machines/AmstradCPC/FDC.hpp +++ b/Machines/AmstradCPC/FDC.hpp @@ -17,32 +17,32 @@ namespace Amstrad { exposes motor control, applying the same value to all drives. */ class FDC: public Intel::i8272::i8272 { - private: - Intel::i8272::BusHandler bus_handler_; +private: + Intel::i8272::BusHandler bus_handler_; - public: - FDC(Cycles clock_rate = Cycles(8000000)) : - i8272(bus_handler_, clock_rate) - { - emplace_drive(clock_rate.as(), 300, 1); - set_drive(1); - } +public: + FDC(Cycles clock_rate = Cycles(8000000)) : + i8272(bus_handler_, clock_rate) + { + emplace_drive(clock_rate.as(), 300, 1); + set_drive(1); + } - void set_motor_on(bool on) { - get_drive().set_motor_on(on); - } + void set_motor_on(bool on) { + get_drive().set_motor_on(on); + } - void select_drive(int) { - // TODO: support more than one drive. (and in set_disk) - } + void select_drive(int) { + // TODO: support more than one drive. (and in set_disk) + } - void set_disk(std::shared_ptr disk, int) { - get_drive().set_disk(disk); - } + void set_disk(std::shared_ptr disk, int) { + get_drive().set_disk(disk); + } - void set_activity_observer(Activity::Observer *observer) { - get_drive().set_activity_observer(observer, "Drive 1", true); - } + void set_activity_observer(Activity::Observer *observer) { + get_drive().set_activity_observer(observer, "Drive 1", true); + } }; } diff --git a/Machines/Apple/ADB/Bus.hpp b/Machines/Apple/ADB/Bus.hpp index 4da64f4f1..46be3a3f5 100644 --- a/Machines/Apple/ADB/Bus.hpp +++ b/Machines/Apple/ADB/Bus.hpp @@ -93,75 +93,75 @@ inline Command decode_command(uint8_t code) { update @c set_device_output. */ class Bus { - public: - Bus(HalfCycles clock_speed); +public: + Bus(HalfCycles clock_speed); - /*! - Advances time; ADB is a clocked serial signal. - */ - void run_for(HalfCycles); + /*! + Advances time; ADB is a clocked serial signal. + */ + void run_for(HalfCycles); - /*! - Adds a device to the bus, returning the index it should use - to refer to itself in subsequent calls to set_device_output. - */ - size_t add_device(); + /*! + Adds a device to the bus, returning the index it should use + to refer to itself in subsequent calls to set_device_output. + */ + size_t add_device(); - /*! - Sets the current data line output for @c device. - */ - void set_device_output(size_t device_id, bool output); + /*! + Sets the current data line output for @c device. + */ + void set_device_output(size_t device_id, bool output); - /*! - @returns The current state of the ADB data line. - */ - bool get_state() const; + /*! + @returns The current state of the ADB data line. + */ + bool get_state() const; - enum class Event { - Reset, - Attention, - Byte, - ServiceRequest, + enum class Event { + Reset, + Attention, + Byte, + ServiceRequest, - Unrecognised - }; + Unrecognised + }; - struct Device { - /// Reports to an observer that @c event was observed in the activity - /// observed on this bus. If this was a byte event, that byte's value is given as @c value. - virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0; + struct Device { + /// Reports to an observer that @c event was observed in the activity + /// observed on this bus. If this was a byte event, that byte's value is given as @c value. + virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0; - /// Requests that the device update itself @c microseconds and, if necessary, post a - /// new value ot @c set_device_output. This will be called only when the bus needs - /// to reevaluate its current level. It cannot reliably be used to track the timing between - /// observed events. - virtual void advance_state(double microseconds, bool current_level) = 0; - }; - /*! - Adds a device. - */ - size_t add_device(Device *); + /// Requests that the device update itself @c microseconds and, if necessary, post a + /// new value ot @c set_device_output. This will be called only when the bus needs + /// to reevaluate its current level. It cannot reliably be used to track the timing between + /// observed events. + virtual void advance_state(double microseconds, bool current_level) = 0; + }; + /*! + Adds a device. + */ + size_t add_device(Device *); - private: - HalfCycles time_in_state_; - mutable HalfCycles time_since_get_state_; +private: + HalfCycles time_in_state_; + mutable HalfCycles time_since_get_state_; - double half_cycles_to_microseconds_ = 1.0; - std::vector devices_; - unsigned int shift_register_ = 0; - unsigned int start_target_ = 8; - bool data_level_ = true; + double half_cycles_to_microseconds_ = 1.0; + std::vector devices_; + unsigned int shift_register_ = 0; + unsigned int start_target_ = 8; + bool data_level_ = true; - // ADB addressing supports at most 16 devices but that doesn't include - // the controller. So assume a maximum of 17 connected devices. - std::bitset<17> bus_state_{0xffffffff}; - size_t next_device_id_ = 0; + // ADB addressing supports at most 16 devices but that doesn't include + // the controller. So assume a maximum of 17 connected devices. + std::bitset<17> bus_state_{0xffffffff}; + size_t next_device_id_ = 0; - inline void shift(unsigned int); - enum class Phase { - PacketCapture, - AttentionCapture - } phase_ = Phase::AttentionCapture; + inline void shift(unsigned int); + enum class Phase { + PacketCapture, + AttentionCapture + } phase_ = Phase::AttentionCapture; }; } diff --git a/Machines/Apple/ADB/Keyboard.hpp b/Machines/Apple/ADB/Keyboard.hpp index 97a50d35f..ba1b4e284 100644 --- a/Machines/Apple/ADB/Keyboard.hpp +++ b/Machines/Apple/ADB/Keyboard.hpp @@ -94,20 +94,20 @@ enum class Key: uint16_t { }; class Keyboard: public ReactiveDevice { - public: - Keyboard(Bus &); +public: + Keyboard(Bus &); - bool set_key_pressed(Key key, bool is_pressed); - void clear_all_keys(); + bool set_key_pressed(Key key, bool is_pressed); + void clear_all_keys(); - private: - void perform_command(const Command &command) override; - void did_receive_data(const Command &, const std::vector &) override; +private: + void perform_command(const Command &command) override; + void did_receive_data(const Command &, const std::vector &) override; - std::mutex keys_mutex_; - std::array pressed_keys_{}; - std::vector pending_events_; - uint16_t modifiers_ = 0xffff; + std::mutex keys_mutex_; + std::array pressed_keys_{}; + std::vector pending_events_; + uint16_t modifiers_ = 0xffff; }; /*! diff --git a/Machines/Apple/ADB/Mouse.hpp b/Machines/Apple/ADB/Mouse.hpp index b7e776b80..06b2a1314 100644 --- a/Machines/Apple/ADB/Mouse.hpp +++ b/Machines/Apple/ADB/Mouse.hpp @@ -14,20 +14,20 @@ namespace Apple::ADB { class Mouse: public ReactiveDevice, public Inputs::Mouse { - public: - Mouse(Bus &); +public: + Mouse(Bus &); - private: - void perform_command(const Command &command) override; +private: + void perform_command(const Command &command) override; - void move(int x, int y) override; - int get_number_of_buttons() const override; - void set_button_pressed(int index, bool is_pressed) override; - void reset_all_buttons() override; + void move(int x, int y) override; + int get_number_of_buttons() const override; + void set_button_pressed(int index, bool is_pressed) override; + void reset_all_buttons() override; - std::atomic delta_x_, delta_y_; - std::atomic button_flags_ = 0; - uint16_t last_posted_reg0_ = 0; + std::atomic delta_x_, delta_y_; + std::atomic button_flags_ = 0; + uint16_t last_posted_reg0_ = 0; }; } diff --git a/Machines/Apple/ADB/ReactiveDevice.hpp b/Machines/Apple/ADB/ReactiveDevice.hpp index 58b165163..99b45032e 100644 --- a/Machines/Apple/ADB/ReactiveDevice.hpp +++ b/Machines/Apple/ADB/ReactiveDevice.hpp @@ -17,45 +17,44 @@ namespace Apple::ADB { class ReactiveDevice: public Bus::Device { - protected: - ReactiveDevice(Bus &bus, uint8_t adb_device_id); +protected: + ReactiveDevice(Bus &bus, uint8_t adb_device_id); - void post_response(const std::vector &&response); - void post_service_request(); - void receive_bytes(size_t count); + void post_response(const std::vector &&response); + void post_service_request(); + void receive_bytes(size_t count); - virtual void perform_command(const Command &command) = 0; - virtual void did_receive_data(const Command &, const std::vector &) {} + virtual void perform_command(const Command &command) = 0; + virtual void did_receive_data(const Command &, const std::vector &) {} - private: - void advance_state(double microseconds, bool current_level) override; - void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override; +private: + void advance_state(double microseconds, bool current_level) override; + void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override; - private: - Bus &bus_; - const size_t device_id_; + Bus &bus_; + const size_t device_id_; - std::vector response_; - int bit_offset_ = 0; - double microseconds_at_bit_ = 0; + std::vector response_; + int bit_offset_ = 0; + double microseconds_at_bit_ = 0; - enum class Phase { - AwaitingAttention, - AwaitingCommand, - AwaitingContent, - ServiceRequestPending, - } phase_ = Phase::AwaitingAttention; - std::vector content_; - size_t expected_content_size_ = 0; - Command command_; - bool stop_has_begin_ = false; + enum class Phase { + AwaitingAttention, + AwaitingCommand, + AwaitingContent, + ServiceRequestPending, + } phase_ = Phase::AwaitingAttention; + std::vector content_; + size_t expected_content_size_ = 0; + Command command_; + bool stop_has_begin_ = false; - uint16_t register3_; - const uint8_t default_adb_device_id_; + uint16_t register3_; + const uint8_t default_adb_device_id_; - std::atomic service_desired_ = false; + std::atomic service_desired_ = false; - void reset(); + void reset(); }; } diff --git a/Machines/Apple/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp index 94642f8e1..e7639dd72 100644 --- a/Machines/Apple/AppleII/AppleII.cpp +++ b/Machines/Apple/AppleII/AppleII.cpp @@ -98,9 +98,9 @@ struct StretchedAYPair: } } - private: - int phase_ = 0; - int subdivider_ = 0; +private: + int phase_ = 0; + int subdivider_ = 0; }; } @@ -120,968 +120,968 @@ template public Configurable::Device, public Activity::Source, public Apple::II::Card::Delegate { - private: - struct VideoBusHandler : public Apple::II::Video::BusHandler { - public: - VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} - - void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { - memcpy(base_target, &ram_[address], count); - memcpy(auxiliary_target, &aux_ram_[address], count); - } - - private: - uint8_t *ram_, *aux_ram_; - }; - - using Processor = CPU::MOS6502::Processor< - (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, - ConcreteMachine, - false>; - Processor m6502_; - VideoBusHandler video_bus_handler_; - Apple::II::Video::Video video_; - int cycles_into_current_line_ = 0; - Cycles cycles_since_video_update_; - - void update_video() { - video_.run_for(cycles_since_video_update_.flush()); - } - static constexpr int audio_divider = has_mockingboard ? 1 : 8; - void update_audio() { - speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); - } - void update_just_in_time_cards() { - if(cycles_since_card_update_ > Cycles(0)) { - for(const auto &card : just_in_time_cards_) { - card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); - } - } - cycles_since_card_update_ = 0; - stretched_cycles_since_card_update_ = 0; - } - - uint8_t ram_[65536], aux_ram_[65536]; - std::vector rom_; - - Concurrency::AsyncTaskQueue audio_queue_; - Audio::Toggle audio_toggle_; - StretchedAYPair ays_; - using SourceT = - std::conditional_t, Audio::Toggle>; - using LowpassT = Outputs::Speaker::PullLowpass; - - Outputs::Speaker::CompoundSource mixer_; - Outputs::Speaker::PullLowpass speaker_; - Cycles cycles_since_audio_update_; - - constexpr SourceT &lowpass_source() { - if constexpr (has_mockingboard) { - return mixer_; - } else { - return audio_toggle_; - } - } - - // MARK: - Cards - static constexpr size_t NoActiveCard = 7; // There is no 'card 0' in internal numbering. - size_t active_card_ = NoActiveCard; - - std::array, 8> cards_; // The final slot is a sentinel for 'no active card'. - Cycles cycles_since_card_update_; - std::vector every_cycle_cards_; - std::vector just_in_time_cards_; - - int stretched_cycles_since_card_update_ = 0; - - void install_card(std::size_t slot, Apple::II::Card *card) { - assert(slot >= 1 && slot < 8); - cards_[slot - 1].reset(card); - card->set_delegate(this); - pick_card_messaging_group(card); - } - - bool is_every_cycle_card(const Apple::II::Card *card) { - return !card->get_select_constraints(); - } - - bool card_lists_are_dirty_ = true; - bool card_became_just_in_time_ = false; - void pick_card_messaging_group(Apple::II::Card *card) { - // Simplify to a card being either just-in-time or realtime. - // Don't worry about exactly what it's watching, - const bool is_every_cycle = is_every_cycle_card(card); - std::vector &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; - - // If the card is already in the proper group, stop. - if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; - - // Otherwise, mark the sets as dirty. It isn't safe to transition the card here, - // as the main loop may be part way through iterating the two lists. - card_lists_are_dirty_ = true; - card_became_just_in_time_ |= !is_every_cycle; - } - - void card_did_change_select_constraints(Apple::II::Card *card) final { - pick_card_messaging_group(card); - } - - void card_did_change_interrupt_flags(Apple::II::Card *) final { - bool nmi = false; - bool irq = false; - - for(const auto &card: cards_) { - if(card) { - nmi |= card->nmi(); - irq |= card->irq(); - } - } - m6502_.set_nmi_line(nmi); - m6502_.set_irq_line(irq); - } - - Apple::II::Mockingboard *mockingboard() { - return dynamic_cast(cards_[MockingboardSlot - 1].get()); - } - - Apple::II::DiskIICard *diskii_card() { - return dynamic_cast(cards_[DiskIISlot - 1].get()); - } - - Apple::II::SCSICard *scsi_card() { - return dynamic_cast(cards_[SCSISlot - 1].get()); - } - - // MARK: - Memory Map. - - /* - The Apple II's paging mechanisms are byzantine to say the least. Painful is - another appropriate adjective. - - On a II and II+ there are five distinct zones of memory: - - 0000 to c000 : the main block of RAM - c000 to d000 : the IO area, including card ROMs - d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card - e000 onward : the rest of ROM, also potentially replaced with RAM by a language card - - On a IIe with auxiliary memory the following orthogonal changes also need to be factored in: - - 0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it - 0400 to 0800 : the text screen, can be configured to write to auxiliary RAM - 2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM - c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area - c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest - c800 to d000 : can contain ROM separately from the region below c800 - - If dealt with as individual blocks in the inner loop, that would therefore imply mapping - an address to one of 13 potential pageable zones. So I've gone reductive and surrendered - to paging every 6502 page of memory independently. It makes the paging events more expensive, - but hopefully more clear. - */ - const uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory - uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. - void page(int start, int end, uint8_t *read, uint8_t *write) { - for(int position = start; position < end; ++position) { - read_pages_[position] = read; - if(read) read += 256; - - write_pages_[position] = write; - if(write) write += 256; - } - } - - // MARK: - The language card, auxiliary memory, and IIe-specific improvements. - LanguageCardSwitches language_card_; - AuxiliaryMemorySwitches auxiliary_switches_; - friend LanguageCardSwitches; - friend AuxiliaryMemorySwitches; - - template void set_paging() { - if constexpr (bool(type & PagingType::ZeroPage)) { - if(auxiliary_switches_.zero_state()) { - write_pages_[0] = aux_ram_; - } else { - write_pages_[0] = ram_; - } - write_pages_[1] = write_pages_[0] + 256; - read_pages_[0] = write_pages_[0]; - read_pages_[1] = write_pages_[1]; - } - - if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage))) { - const auto language_state = language_card_.state(); - const auto zero_state = auxiliary_switches_.zero_state(); - - uint8_t *const ram = zero_state ? aux_ram_ : ram_; - uint8_t *const rom = is_iie(model) ? &rom_[3840] : rom_.data(); - - // Which way the region here is mapped to be banks 1 and 2 is - // arbitrary. - page(0xd0, 0xe0, - language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom, - language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]); - - page(0xe0, 0x100, - language_state.read ? &ram[0xe000] : &rom[0x1000], - language_state.write ? nullptr : &ram[0xe000]); - } - - if constexpr (bool(type & PagingType::CardArea)) { - const auto state = auxiliary_switches_.card_state(); - - page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr); - read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr; - page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr); - page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr); - } - - if constexpr (bool(type & PagingType::Main)) { - const auto state = auxiliary_switches_.main_state(); - - page(0x02, 0x04, - state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200], - state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]); - page(0x08, 0x20, - state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800], - state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]); - page(0x40, 0xc0, - state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000], - state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]); - - page(0x04, 0x08, - state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400], - state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]); - - page(0x20, 0x40, - state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000], - state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]); - } - } - - // MARK: - Keyboard and typing. - - struct Keyboard: public Inputs::Keyboard { - Keyboard(Processor &m6502, AuxiliaryMemorySwitches &switches) : m6502_(m6502), auxiliary_switches_(switches) {} - - void reset_all_keys() final { - open_apple_is_pressed = - closed_apple_is_pressed = - control_is_pressed_ = - shift_is_pressed_ = - repeat_is_pressed_ = - key_is_down_ = - character_is_pressed_ = false; - } - - bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final { - if constexpr (!is_iie(model)) { - if(is_repeat && !repeat_is_pressed_) return true; - } - - // If no ASCII value is supplied, look for a few special cases. - switch(key) { - case Key::Left: value = 0x08; break; - case Key::Right: value = 0x15; break; - case Key::Down: value = 0x0a; break; - case Key::Up: value = 0x0b; break; - case Key::Backspace: - if(is_iie(model)) { - value = 0x7f; - break; - } else { - return false; - } - case Key::Enter: value = 0x0d; break; - case Key::Tab: - if (is_iie(model)) { - value = '\t'; - break; - } else { - return false; - } - case Key::Escape: value = 0x1b; break; - case Key::Space: value = 0x20; break; - - case Key::LeftOption: - case Key::RightMeta: - if (is_iie(model)) { - open_apple_is_pressed = is_pressed; - return true; - } else { - return false; - } - - case Key::RightOption: - case Key::LeftMeta: - if (is_iie(model)) { - closed_apple_is_pressed = is_pressed; - return true; - } else { - return false; - } - - case Key::LeftControl: - control_is_pressed_ = is_pressed; - return true; - - case Key::LeftShift: - case Key::RightShift: - shift_is_pressed_ = is_pressed; - return true; - - case Key::F1: case Key::F2: case Key::F3: case Key::F4: - case Key::F5: case Key::F6: case Key::F7: case Key::F8: - case Key::F9: case Key::F10: case Key::F11: - repeat_is_pressed_ = is_pressed; - - if constexpr (!is_iie(model)) { - if(is_pressed && (!is_repeat || character_is_pressed_)) { - keyboard_input_ = uint8_t(last_pressed_character_ | 0x80); - } - } - return true; - - case Key::F12: - case Key::PrintScreen: - case Key::ScrollLock: - case Key::Pause: - case Key::Insert: - case Key::Home: - case Key::PageUp: - case Key::PageDown: - case Key::End: - // Accept a bunch non-symbolic other keys, as - // reset, in the hope that the user can find - // at least one usable key. - m6502_.set_reset_line(is_pressed); - if(!is_pressed) { - auxiliary_switches_.reset(); - } - return true; - - default: - if(!value) { - return false; - } - - // Prior to the IIe, the keyboard could produce uppercase only. - if(!is_iie(model)) value = char(toupper(value)); - - if(control_is_pressed_ && isalpha(value)) value &= 0xbf; - - // TODO: properly map IIe keys - if(!is_iie(model) && shift_is_pressed_) { - switch(value) { - case 0x27: value = 0x22; break; // ' -> " - case 0x2c: value = 0x3c; break; // , -> < - case 0x2e: value = 0x3e; break; // . -> > - case 0x2f: value = 0x3f; break; // / -> ? - case 0x30: value = 0x29; break; // 0 -> ) - case 0x31: value = 0x21; break; // 1 -> ! - case 0x32: value = 0x40; break; // 2 -> @ - case 0x33: value = 0x23; break; // 3 -> # - case 0x34: value = 0x24; break; // 4 -> $ - case 0x35: value = 0x25; break; // 5 -> % - case 0x36: value = 0x5e; break; // 6 -> ^ - case 0x37: value = 0x26; break; // 7 -> & - case 0x38: value = 0x2a; break; // 8 -> * - case 0x39: value = 0x28; break; // 9 -> ( - case 0x3b: value = 0x3a; break; // ; -> : - case 0x3d: value = 0x2b; break; // = -> + - } - } - break; - } - - if(is_pressed) { - last_pressed_character_ = value; - character_is_pressed_ = true; - keyboard_input_ = uint8_t(value | 0x80); - key_is_down_ = true; - } else { - if(value == last_pressed_character_) { - character_is_pressed_ = false; - } - if((keyboard_input_ & 0x3f) == value) { - key_is_down_ = false; - } - } - - return true; - } - - uint8_t get_keyboard_input() { - if(string_serialiser_) { - return string_serialiser_->head() | 0x80; - } else { - return keyboard_input_; - } - } - - void clear_keyboard_input() { - keyboard_input_ &= 0x7f; - if(string_serialiser_ && !string_serialiser_->advance()) { - string_serialiser_.reset(); - } - } - - bool get_key_is_down() { - return key_is_down_; - } - - void set_string_serialiser(std::unique_ptr &&serialiser) { - string_serialiser_ = std::move(serialiser); - } - - // The IIe has three keys that are wired directly to the same input as the joystick buttons. - bool open_apple_is_pressed = false; - bool closed_apple_is_pressed = false; - - private: - // Current keyboard input register, as exposed to the programmer; on the IIe the programmer - // can also poll for whether any key is currently down. - uint8_t keyboard_input_ = 0x00; - bool key_is_down_ = false; - - // ASCII input state, referenced by the REPT key on models before the IIe. - char last_pressed_character_ = 0; - bool character_is_pressed_ = false; - - // The repeat key itself. - bool repeat_is_pressed_ = false; - - // Modifier states. - bool shift_is_pressed_ = false; - bool control_is_pressed_ = false; - - // A string serialiser for receiving copy and paste. - std::unique_ptr string_serialiser_; - - // 6502 connection, for applying the reset button. - Processor &m6502_; - AuxiliaryMemorySwitches &auxiliary_switches_; - }; - Keyboard keyboard_; - - // MARK: - Joysticks. - JoystickPair joysticks_; - +private: + struct VideoBusHandler : public Apple::II::Video::BusHandler { public: - ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): - m6502_(*this), - video_bus_handler_(ram_, aux_ram_), - video_(video_bus_handler_), - audio_toggle_(audio_queue_), - ays_(audio_queue_), - mixer_(ays_, audio_toggle_), - speaker_(lowpass_source()), - language_card_(*this), - auxiliary_switches_(*this), - keyboard_(m6502_, auxiliary_switches_) { + VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} - // This is where things get slightly convoluted: establish the machine as having a clock rate - // equal to the number of cycles of work the 6502 will actually achieve. Which is less than - // the master clock rate divided by 14 because every 65th cycle is extended by one seventh. - set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0)); - - // The speaker, however, should think it is clocked at half the master clock, per a general - // decision to sample it at seven times the CPU clock (plus stretches). - speaker_.set_input_rate(float(master_clock / (2.0 * float(audio_divider)))); - - // Apply a 6Khz low-pass filter. This was picked by ear and by an attempt to understand the - // Apple II schematic but, well, I don't claim much insight on the latter. This is definitely - // something to review in the future. - speaker_.set_high_frequency_cutoff(6000); - - // Also, start with randomised memory contents. - Memory::Fuzz(ram_, sizeof(ram_)); - Memory::Fuzz(aux_ram_, sizeof(aux_ram_)); - - // Pick the required ROMs. - using Target = Analyser::Static::AppleII::Target; - ROM::Name character, system; - - switch(target.model) { - default: - character = ROM::Name::AppleIICharacter; - system = ROM::Name::AppleIIOriginal; - break; - case Target::Model::IIplus: - character = ROM::Name::AppleIICharacter; - system = ROM::Name::AppleIIPlus; - break; - case Target::Model::IIe: - character = ROM::Name::AppleIIeCharacter; - system = ROM::Name::AppleIIe; - break; - case Target::Model::EnhancedIIe: - character = ROM::Name::AppleIIEnhancedECharacter; - system = ROM::Name::AppleIIEnhancedE; - break; - } - - ROM::Request request = ROM::Request(character) && ROM::Request(system); - - // Add the necessary Disk II requests if appropriate. - const bool has_disk_controller = target.disk_controller != Target::DiskController::None; - const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector; - if(has_disk_controller) { - request = request && DiskIICard::rom_request(is_sixteen_sector); - } - - // Add a SCSI card if requested. - const bool has_scsi_card = target.scsi_controller == Target::SCSIController::AppleSCSI; - if(has_scsi_card) { - request = request && SCSICard::rom_request(); - } - - // Request, validate and install ROMs. - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - - if(has_disk_controller) { - install_card(DiskIISlot, new Apple::II::DiskIICard(roms, is_sixteen_sector)); - } - - if(has_scsi_card) { - // Rounding the clock rate slightly shouldn't matter, but: - // TODO: be [slightly] more honest about clock rate. - install_card(SCSISlot, new Apple::II::SCSICard(roms, int(master_clock / 14.0f))); - } - - if(target.has_mockingboard) { - // The Mockingboard has a parasitic relationship with this class due to the way - // that audio outputs are implemented in this emulator. - install_card(MockingboardSlot, new Apple::II::Mockingboard(ays_)); - } - - rom_ = std::move(roms.find(system)->second); - // The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary. - if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) { - if(rom_.size() > 16128) { - rom_.erase(rom_.begin(), rom_.end() - 16128); - } - } - video_.set_character_rom(roms.find(character)->second); - - // Set up the default memory blocks. On a II or II+ these values will never change. - // On a IIe they'll be affected by selection of auxiliary RAM. - set_paging(); - - // Set the whole card area to initially backed by nothing. - page(0xc0, 0xd0, nullptr, nullptr); - - insert_media(target.media); + void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) { + memcpy(base_target, &ram_[address], count); + memcpy(auxiliary_target, &aux_ram_[address], count); } - ~ConcreteMachine() { - audio_queue_.flush(); - } + private: + uint8_t *ram_, *aux_ram_; + }; - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - video_.set_scan_target(scan_target); - } + using Processor = CPU::MOS6502::Processor< + (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, + ConcreteMachine, + false>; + Processor m6502_; + VideoBusHandler video_bus_handler_; + Apple::II::Video::Video video_; + int cycles_into_current_line_ = 0; + Cycles cycles_since_video_update_; - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return video_.get_scaled_scan_status(); - } - - /// Sets the type of display. - void set_display_type(Outputs::Display::DisplayType display_type) final { - video_.set_display_type(display_type); - } - - Outputs::Display::DisplayType get_display_type() const final { - return video_.get_display_type(); - } - - Outputs::Speaker::Speaker *get_speaker() final { - return &speaker_; - } - - forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) { - ++ cycles_since_video_update_; - ++ cycles_since_card_update_; - cycles_since_audio_update_ += Cycles(7); - - // The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched - // by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after - // 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary - // signal approximation that produces colour needs to be in phase, so a stretch of exactly - // 0.5 further colour cycles is added. The video class handles that implicitly, but it - // needs to be accumulated here for the audio. - cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65; - const bool is_stretched_cycle = !cycles_into_current_line_; - if(is_stretched_cycle) { - ++ cycles_since_audio_update_; - ++ stretched_cycles_since_card_update_; + void update_video() { + video_.run_for(cycles_since_video_update_.flush()); + } + static constexpr int audio_divider = has_mockingboard ? 1 : 8; + void update_audio() { + speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); + } + void update_just_in_time_cards() { + if(cycles_since_card_update_ > Cycles(0)) { + for(const auto &card : just_in_time_cards_) { + card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_); } + } + cycles_since_card_update_ = 0; + stretched_cycles_since_card_update_ = 0; + } - bool has_updated_cards = false; - if(read_pages_[address >> 8]) { - if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; - else { - if(address >= 0x200 && address < 0x6000) update_video(); - if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; - } + uint8_t ram_[65536], aux_ram_[65536]; + std::vector rom_; - if(is_iie(model)) { - auxiliary_switches_.access(address, isReadOperation(operation)); - } + Concurrency::AsyncTaskQueue audio_queue_; + Audio::Toggle audio_toggle_; + StretchedAYPair ays_; + using SourceT = + std::conditional_t, Audio::Toggle>; + using LowpassT = Outputs::Speaker::PullLowpass; + + Outputs::Speaker::CompoundSource mixer_; + Outputs::Speaker::PullLowpass speaker_; + Cycles cycles_since_audio_update_; + + constexpr SourceT &lowpass_source() { + if constexpr (has_mockingboard) { + return mixer_; + } else { + return audio_toggle_; + } + } + + // MARK: - Cards + static constexpr size_t NoActiveCard = 7; // There is no 'card 0' in internal numbering. + size_t active_card_ = NoActiveCard; + + std::array, 8> cards_; // The final slot is a sentinel for 'no active card'. + Cycles cycles_since_card_update_; + std::vector every_cycle_cards_; + std::vector just_in_time_cards_; + + int stretched_cycles_since_card_update_ = 0; + + void install_card(std::size_t slot, Apple::II::Card *card) { + assert(slot >= 1 && slot < 8); + cards_[slot - 1].reset(card); + card->set_delegate(this); + pick_card_messaging_group(card); + } + + bool is_every_cycle_card(const Apple::II::Card *card) { + return !card->get_select_constraints(); + } + + bool card_lists_are_dirty_ = true; + bool card_became_just_in_time_ = false; + void pick_card_messaging_group(Apple::II::Card *card) { + // Simplify to a card being either just-in-time or realtime. + // Don't worry about exactly what it's watching, + const bool is_every_cycle = is_every_cycle_card(card); + std::vector &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; + + // If the card is already in the proper group, stop. + if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; + + // Otherwise, mark the sets as dirty. It isn't safe to transition the card here, + // as the main loop may be part way through iterating the two lists. + card_lists_are_dirty_ = true; + card_became_just_in_time_ |= !is_every_cycle; + } + + void card_did_change_select_constraints(Apple::II::Card *card) final { + pick_card_messaging_group(card); + } + + void card_did_change_interrupt_flags(Apple::II::Card *) final { + bool nmi = false; + bool irq = false; + + for(const auto &card: cards_) { + if(card) { + nmi |= card->nmi(); + irq |= card->irq(); + } + } + m6502_.set_nmi_line(nmi); + m6502_.set_irq_line(irq); + } + + Apple::II::Mockingboard *mockingboard() { + return dynamic_cast(cards_[MockingboardSlot - 1].get()); + } + + Apple::II::DiskIICard *diskii_card() { + return dynamic_cast(cards_[DiskIISlot - 1].get()); + } + + Apple::II::SCSICard *scsi_card() { + return dynamic_cast(cards_[SCSISlot - 1].get()); + } + + // MARK: - Memory Map. + + /* + The Apple II's paging mechanisms are byzantine to say the least. Painful is + another appropriate adjective. + + On a II and II+ there are five distinct zones of memory: + + 0000 to c000 : the main block of RAM + c000 to d000 : the IO area, including card ROMs + d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card + e000 onward : the rest of ROM, also potentially replaced with RAM by a language card + + On a IIe with auxiliary memory the following orthogonal changes also need to be factored in: + + 0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it + 0400 to 0800 : the text screen, can be configured to write to auxiliary RAM + 2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM + c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area + c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest + c800 to d000 : can contain ROM separately from the region below c800 + + If dealt with as individual blocks in the inner loop, that would therefore imply mapping + an address to one of 13 potential pageable zones. So I've gone reductive and surrendered + to paging every 6502 page of memory independently. It makes the paging events more expensive, + but hopefully more clear. + */ + const uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory + uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write. + void page(int start, int end, uint8_t *read, uint8_t *write) { + for(int position = start; position < end; ++position) { + read_pages_[position] = read; + if(read) read += 256; + + write_pages_[position] = write; + if(write) write += 256; + } + } + + // MARK: - The language card, auxiliary memory, and IIe-specific improvements. + LanguageCardSwitches language_card_; + AuxiliaryMemorySwitches auxiliary_switches_; + friend LanguageCardSwitches; + friend AuxiliaryMemorySwitches; + + template void set_paging() { + if constexpr (bool(type & PagingType::ZeroPage)) { + if(auxiliary_switches_.zero_state()) { + write_pages_[0] = aux_ram_; } else { - // Assume a vapour read unless it turns out otherwise; this is a little - // wasteful but works for now. - // - // Longer version: like many other machines, when the Apple II reads from - // an address at which no hardware loads the data bus, through a process of - // practical analogue effects it'll end up receiving whatever was last on - // the bus. Which will always be whatever the video circuit fetched because - // that fetches in between every instruction. - // - // So this code assumes that'll happen unless it later determines that it - // doesn't. The call into the video isn't free because it's a just-in-time - // actor, but this will actually be the result most of the time so it's not - // too terrible. - if(isReadOperation(operation) && address != 0xc000) { - // Ensure any enqueued video changes are applied before grabbing the - // vapour value. - if(video_.has_deferred_actions()) { - update_video(); - } - *value = video_.get_last_read_value(cycles_since_video_update_); - } + write_pages_[0] = ram_; + } + write_pages_[1] = write_pages_[0] + 256; + read_pages_[0] = write_pages_[0]; + read_pages_[1] = write_pages_[1]; + } - switch(address) { - default: - if(isReadOperation(operation)) { - // Read-only switches. - switch(address) { - default: break; + if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage))) { + const auto language_state = language_card_.state(); + const auto zero_state = auxiliary_switches_.zero_state(); - case 0xc000: - *value = keyboard_.get_keyboard_input(); - break; - case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007: - case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f: - *value = (*value & 0x80) | (keyboard_.get_keyboard_input() & 0x7f); - break; + uint8_t *const ram = zero_state ? aux_ram_ : ram_; + uint8_t *const rom = is_iie(model) ? &rom_[3840] : rom_.data(); - case 0xc061: // Switch input 0. - *value &= 0x7f; - if( - joysticks_.button(0) || - (is_iie(model) && keyboard_.open_apple_is_pressed) - ) - *value |= 0x80; - break; - case 0xc062: // Switch input 1. - *value &= 0x7f; - if( - joysticks_.button(1) || - (is_iie(model) && keyboard_.closed_apple_is_pressed) - ) - *value |= 0x80; - break; - case 0xc063: // Switch input 2. - *value &= 0x7f; - if(joysticks_.button(2)) - *value |= 0x80; - break; + // Which way the region here is mapped to be banks 1 and 2 is + // arbitrary. + page(0xd0, 0xe0, + language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom, + language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]); - case 0xc064: // Analogue input 0. - case 0xc065: // Analogue input 1. - case 0xc066: // Analogue input 2. - case 0xc067: { // Analogue input 3. - const size_t input = address - 0xc064; - *value &= 0x7f; - if(!joysticks_.analogue_channel_is_discharged(input)) { - *value |= 0x80; - } - } break; + page(0xe0, 0x100, + language_state.read ? &ram[0xe000] : &rom[0x1000], + language_state.write ? nullptr : &ram[0xe000]); + } - // The IIe-only state reads follow... -#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie(model)) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); - case 0xc011: IIeSwitchRead(language_card_.state().bank2); break; - case 0xc012: IIeSwitchRead(language_card_.state().read); break; - case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break; - case 0xc014: IIeSwitchRead(auxiliary_switches_.switches().write_auxiliary_memory); break; - case 0xc015: IIeSwitchRead(auxiliary_switches_.switches().internal_CX_rom); break; - case 0xc016: IIeSwitchRead(auxiliary_switches_.switches().alternative_zero_page); break; - case 0xc017: IIeSwitchRead(auxiliary_switches_.switches().slot_C3_rom); break; - case 0xc018: IIeSwitchRead(video_.get_80_store()); break; - case 0xc019: IIeSwitchRead(video_.get_is_vertical_blank(cycles_since_video_update_)); break; - case 0xc01a: IIeSwitchRead(video_.get_text()); break; - case 0xc01b: IIeSwitchRead(video_.get_mixed()); break; - case 0xc01c: IIeSwitchRead(video_.get_page2()); break; - case 0xc01d: IIeSwitchRead(video_.get_high_resolution()); break; - case 0xc01e: IIeSwitchRead(video_.get_alternative_character_set()); break; - case 0xc01f: IIeSwitchRead(video_.get_80_columns()); break; -#undef IIeSwitchRead + if constexpr (bool(type & PagingType::CardArea)) { + const auto state = auxiliary_switches_.card_state(); - case 0xc07f: - if(is_iie(model)) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00); - break; - } - } else { - // Write-only switches. All IIe as currently implemented. - if(is_iie(model)) { - auxiliary_switches_.access(address, false); - switch(address) { - default: break; + page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr); + read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr; + page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr); + page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr); + } - case 0xc000: - case 0xc001: - update_video(); - video_.set_80_store(address&1); - break; + if constexpr (bool(type & PagingType::Main)) { + const auto state = auxiliary_switches_.main_state(); - case 0xc00c: - case 0xc00d: - update_video(); - video_.set_80_columns(address&1); - break; + page(0x02, 0x04, + state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200], + state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]); + page(0x08, 0x20, + state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800], + state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]); + page(0x40, 0xc0, + state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000], + state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]); - case 0xc00e: - case 0xc00f: - update_video(); - video_.set_alternative_character_set(address&1); - break; - } - } - } - break; + page(0x04, 0x08, + state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400], + state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]); - case 0xc070: joysticks_.access_c070(); break; + page(0x20, 0x40, + state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000], + state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]); + } + } - /* Switches triggered by reading or writing. */ - case 0xc050: - case 0xc051: - update_video(); - video_.set_text(address&1); - break; - case 0xc052: update_video(); video_.set_mixed(false); break; - case 0xc053: update_video(); video_.set_mixed(true); break; - case 0xc054: - case 0xc055: - update_video(); - video_.set_page2(address&1); - auxiliary_switches_.access(address, isReadOperation(operation)); - break; - case 0xc056: - case 0xc057: - update_video(); - video_.set_high_resolution(address&1); - auxiliary_switches_.access(address, isReadOperation(operation)); - break; + // MARK: - Keyboard and typing. - case 0xc05e: - case 0xc05f: - if(is_iie(model)) { - update_video(); - video_.set_annunciator_3(!(address&1)); - } - break; + struct Keyboard: public Inputs::Keyboard { + Keyboard(Processor &m6502, AuxiliaryMemorySwitches &switches) : m6502_(m6502), auxiliary_switches_(switches) {} - case 0xc010: - keyboard_.clear_keyboard_input(); + void reset_all_keys() final { + open_apple_is_pressed = + closed_apple_is_pressed = + control_is_pressed_ = + shift_is_pressed_ = + repeat_is_pressed_ = + key_is_down_ = + character_is_pressed_ = false; + } - // On the IIe, reading C010 returns additional key info. - if(is_iie(model) && isReadOperation(operation)) { - *value = (keyboard_.get_key_is_down() ? 0x80 : 0x00) | (keyboard_.get_keyboard_input() & 0x7f); - } - break; + bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final { + if constexpr (!is_iie(model)) { + if(is_repeat && !repeat_is_pressed_) return true; + } - case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037: - case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f: - update_audio(); - audio_toggle_.set_output(!audio_toggle_.get_output()); - break; - - case 0xc080: case 0xc084: case 0xc088: case 0xc08c: - case 0xc081: case 0xc085: case 0xc089: case 0xc08d: - case 0xc082: case 0xc086: case 0xc08a: case 0xc08e: - case 0xc083: case 0xc087: case 0xc08b: case 0xc08f: - language_card_.access(address, isReadOperation(operation)); - break; - } - - /* - Communication with cards follows. - */ - - if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xd000) { - // If this is a card access, figure out which card is at play before determining - // the totality of who needs messaging. - size_t card_number = 0; - Apple::II::Card::Select select = Apple::II::Card::None; - - if(address >= 0xc800) { - /* - Decode the 2kb area used for additional ROMs. - This is shared by all cards. - */ - card_number = active_card_; - select = Apple::II::Card::C8Region; - - // An access to $cfff will disable the active card. - if(address == 0xcfff) { - active_card_ = NoActiveCard; - } - } else if(address >= 0xc100) { - /* - Decode the area conventionally used by cards for ROMs: - 0xCn00 to 0xCnff: card n. - - This also sets the active card for the C8 region. - */ - active_card_ = card_number = (address - 0xc100) >> 8; - select = Apple::II::Card::IO; + // If no ASCII value is supplied, look for a few special cases. + switch(key) { + case Key::Left: value = 0x08; break; + case Key::Right: value = 0x15; break; + case Key::Down: value = 0x0a; break; + case Key::Up: value = 0x0b; break; + case Key::Backspace: + if(is_iie(model)) { + value = 0x7f; + break; } else { - /* - Decode the area conventionally used by cards for registers: - C0n0 to C0nF: card n - 8. - */ - card_number = (address - 0xc090) >> 4; - select = Apple::II::Card::Device; + return false; } - - // If the selected card is a just-in-time card, update the just-in-time cards, - // and then message it specifically. - const bool is_read = isReadOperation(operation); - Apple::II::Card *const target = cards_[size_t(card_number)].get(); - if(target && !is_every_cycle_card(target)) { - update_just_in_time_cards(); - target->perform_bus_operation(select, is_read, address, value); - } - - // Update all the every-cycle cards regardless, but send them a ::None select if they're - // not the one actually selected. - for(const auto &card: every_cycle_cards_) { - card->run_for(Cycles(1), is_stretched_cycle); - card->perform_bus_operation( - (card == target) ? select : Apple::II::Card::None, - is_read, address, value); - } - has_updated_cards = true; - } - } - - if(!has_updated_cards && !every_cycle_cards_.empty()) { - // Update all every-cycle cards and give them the cycle. - const bool is_read = isReadOperation(operation); - for(const auto &card: every_cycle_cards_) { - card->run_for(Cycles(1), is_stretched_cycle); - card->perform_bus_operation(Apple::II::Card::None, is_read, address, value); - } - } - - // Update the card lists if any mutations are due. - if(card_lists_are_dirty_) { - card_lists_are_dirty_ = false; - - // There's only one counter of time since update - // for just-in-time cards. If something new is - // transitioning, that needs to be zeroed. - if(card_became_just_in_time_) { - card_became_just_in_time_ = false; - update_just_in_time_cards(); - } - - // Clear the two lists and repopulate. - every_cycle_cards_.clear(); - just_in_time_cards_.clear(); - for(const auto &card: cards_) { - if(!card) continue; - if(is_every_cycle_card(card.get())) { - every_cycle_cards_.push_back(card.get()); + case Key::Enter: value = 0x0d; break; + case Key::Tab: + if (is_iie(model)) { + value = '\t'; + break; } else { - just_in_time_cards_.push_back(card.get()); + return false; + } + case Key::Escape: value = 0x1b; break; + case Key::Space: value = 0x20; break; + + case Key::LeftOption: + case Key::RightMeta: + if (is_iie(model)) { + open_apple_is_pressed = is_pressed; + return true; + } else { + return false; + } + + case Key::RightOption: + case Key::LeftMeta: + if (is_iie(model)) { + closed_apple_is_pressed = is_pressed; + return true; + } else { + return false; + } + + case Key::LeftControl: + control_is_pressed_ = is_pressed; + return true; + + case Key::LeftShift: + case Key::RightShift: + shift_is_pressed_ = is_pressed; + return true; + + case Key::F1: case Key::F2: case Key::F3: case Key::F4: + case Key::F5: case Key::F6: case Key::F7: case Key::F8: + case Key::F9: case Key::F10: case Key::F11: + repeat_is_pressed_ = is_pressed; + + if constexpr (!is_iie(model)) { + if(is_pressed && (!is_repeat || character_is_pressed_)) { + keyboard_input_ = uint8_t(last_pressed_character_ | 0x80); + } + } + return true; + + case Key::F12: + case Key::PrintScreen: + case Key::ScrollLock: + case Key::Pause: + case Key::Insert: + case Key::Home: + case Key::PageUp: + case Key::PageDown: + case Key::End: + // Accept a bunch non-symbolic other keys, as + // reset, in the hope that the user can find + // at least one usable key. + m6502_.set_reset_line(is_pressed); + if(!is_pressed) { + auxiliary_switches_.reset(); + } + return true; + + default: + if(!value) { + return false; + } + + // Prior to the IIe, the keyboard could produce uppercase only. + if(!is_iie(model)) value = char(toupper(value)); + + if(control_is_pressed_ && isalpha(value)) value &= 0xbf; + + // TODO: properly map IIe keys + if(!is_iie(model) && shift_is_pressed_) { + switch(value) { + case 0x27: value = 0x22; break; // ' -> " + case 0x2c: value = 0x3c; break; // , -> < + case 0x2e: value = 0x3e; break; // . -> > + case 0x2f: value = 0x3f; break; // / -> ? + case 0x30: value = 0x29; break; // 0 -> ) + case 0x31: value = 0x21; break; // 1 -> ! + case 0x32: value = 0x40; break; // 2 -> @ + case 0x33: value = 0x23; break; // 3 -> # + case 0x34: value = 0x24; break; // 4 -> $ + case 0x35: value = 0x25; break; // 5 -> % + case 0x36: value = 0x5e; break; // 6 -> ^ + case 0x37: value = 0x26; break; // 7 -> & + case 0x38: value = 0x2a; break; // 8 -> * + case 0x39: value = 0x28; break; // 9 -> ( + case 0x3b: value = 0x3a; break; // ; -> : + case 0x3d: value = 0x2b; break; // = -> + } } + break; } - // Update analogue charge level. - joysticks_.update_charge(); - - return Cycles(1); - } - - void flush_output(int outputs) final { - update_just_in_time_cards(); - - if(outputs & Output::Video) { - update_video(); - } - if(outputs & Output::Audio) { - update_audio(); - audio_queue_.perform(); - } - } - - void run_for(const Cycles cycles) final { - m6502_.run_for(cycles); - } - - bool prefers_logical_input() final { - return is_iie(model); - } - - Inputs::Keyboard &get_keyboard() final { - return keyboard_; - } - - void type_string(const std::string &string) final { - keyboard_.set_string_serialiser(std::make_unique(string, true)); - } - - bool can_type(char c) const final { - // Make an effort to type the entire printable ASCII range. - return c >= 32 && c < 127; - } - - // MARK:: Configuration options. - std::unique_ptr get_options() const final { - auto options = std::make_unique(Configurable::OptionsType::UserFriendly); - options->output = get_video_signal_configurable(); - options->use_square_pixels = video_.get_use_square_pixels(); - return options; - } - - void set_options(const std::unique_ptr &str) { - const auto options = dynamic_cast(str.get()); - set_video_signal_configurable(options->output); - video_.set_use_square_pixels(options->use_square_pixels); - } - - // MARK: MediaTarget - bool insert_media(const Analyser::Static::Media &media) final { - if(!media.disks.empty()) { - auto diskii = diskii_card(); - if(diskii) diskii->set_disk(media.disks[0], 0); - } - - if(!media.mass_storage_devices.empty()) { - auto scsi = scsi_card(); - if(scsi) scsi->set_storage_device(media.mass_storage_devices[0]); + if(is_pressed) { + last_pressed_character_ = value; + character_is_pressed_ = true; + keyboard_input_ = uint8_t(value | 0x80); + key_is_down_ = true; + } else { + if(value == last_pressed_character_) { + character_is_pressed_ = false; + } + if((keyboard_input_ & 0x3f) == value) { + key_is_down_ = false; + } } return true; } - // MARK: Activity::Source - void set_activity_observer(Activity::Observer *observer) final { - for(const auto &card: cards_) { - if(card) card->set_activity_observer(observer); + uint8_t get_keyboard_input() { + if(string_serialiser_) { + return string_serialiser_->head() | 0x80; + } else { + return keyboard_input_; } } - // MARK: JoystickMachine - const std::vector> &get_joysticks() final { - return joysticks_.get_joysticks(); + void clear_keyboard_input() { + keyboard_input_ &= 0x7f; + if(string_serialiser_ && !string_serialiser_->advance()) { + string_serialiser_.reset(); + } } + + bool get_key_is_down() { + return key_is_down_; + } + + void set_string_serialiser(std::unique_ptr &&serialiser) { + string_serialiser_ = std::move(serialiser); + } + + // The IIe has three keys that are wired directly to the same input as the joystick buttons. + bool open_apple_is_pressed = false; + bool closed_apple_is_pressed = false; + + private: + // Current keyboard input register, as exposed to the programmer; on the IIe the programmer + // can also poll for whether any key is currently down. + uint8_t keyboard_input_ = 0x00; + bool key_is_down_ = false; + + // ASCII input state, referenced by the REPT key on models before the IIe. + char last_pressed_character_ = 0; + bool character_is_pressed_ = false; + + // The repeat key itself. + bool repeat_is_pressed_ = false; + + // Modifier states. + bool shift_is_pressed_ = false; + bool control_is_pressed_ = false; + + // A string serialiser for receiving copy and paste. + std::unique_ptr string_serialiser_; + + // 6502 connection, for applying the reset button. + Processor &m6502_; + AuxiliaryMemorySwitches &auxiliary_switches_; + }; + Keyboard keyboard_; + + // MARK: - Joysticks. + JoystickPair joysticks_; + +public: + ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher): + m6502_(*this), + video_bus_handler_(ram_, aux_ram_), + video_(video_bus_handler_), + audio_toggle_(audio_queue_), + ays_(audio_queue_), + mixer_(ays_, audio_toggle_), + speaker_(lowpass_source()), + language_card_(*this), + auxiliary_switches_(*this), + keyboard_(m6502_, auxiliary_switches_) { + + // This is where things get slightly convoluted: establish the machine as having a clock rate + // equal to the number of cycles of work the 6502 will actually achieve. Which is less than + // the master clock rate divided by 14 because every 65th cycle is extended by one seventh. + set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0)); + + // The speaker, however, should think it is clocked at half the master clock, per a general + // decision to sample it at seven times the CPU clock (plus stretches). + speaker_.set_input_rate(float(master_clock / (2.0 * float(audio_divider)))); + + // Apply a 6Khz low-pass filter. This was picked by ear and by an attempt to understand the + // Apple II schematic but, well, I don't claim much insight on the latter. This is definitely + // something to review in the future. + speaker_.set_high_frequency_cutoff(6000); + + // Also, start with randomised memory contents. + Memory::Fuzz(ram_, sizeof(ram_)); + Memory::Fuzz(aux_ram_, sizeof(aux_ram_)); + + // Pick the required ROMs. + using Target = Analyser::Static::AppleII::Target; + ROM::Name character, system; + + switch(target.model) { + default: + character = ROM::Name::AppleIICharacter; + system = ROM::Name::AppleIIOriginal; + break; + case Target::Model::IIplus: + character = ROM::Name::AppleIICharacter; + system = ROM::Name::AppleIIPlus; + break; + case Target::Model::IIe: + character = ROM::Name::AppleIIeCharacter; + system = ROM::Name::AppleIIe; + break; + case Target::Model::EnhancedIIe: + character = ROM::Name::AppleIIEnhancedECharacter; + system = ROM::Name::AppleIIEnhancedE; + break; + } + + ROM::Request request = ROM::Request(character) && ROM::Request(system); + + // Add the necessary Disk II requests if appropriate. + const bool has_disk_controller = target.disk_controller != Target::DiskController::None; + const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector; + if(has_disk_controller) { + request = request && DiskIICard::rom_request(is_sixteen_sector); + } + + // Add a SCSI card if requested. + const bool has_scsi_card = target.scsi_controller == Target::SCSIController::AppleSCSI; + if(has_scsi_card) { + request = request && SCSICard::rom_request(); + } + + // Request, validate and install ROMs. + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + + if(has_disk_controller) { + install_card(DiskIISlot, new Apple::II::DiskIICard(roms, is_sixteen_sector)); + } + + if(has_scsi_card) { + // Rounding the clock rate slightly shouldn't matter, but: + // TODO: be [slightly] more honest about clock rate. + install_card(SCSISlot, new Apple::II::SCSICard(roms, int(master_clock / 14.0f))); + } + + if(target.has_mockingboard) { + // The Mockingboard has a parasitic relationship with this class due to the way + // that audio outputs are implemented in this emulator. + install_card(MockingboardSlot, new Apple::II::Mockingboard(ays_)); + } + + rom_ = std::move(roms.find(system)->second); + // The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary. + if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) { + if(rom_.size() > 16128) { + rom_.erase(rom_.begin(), rom_.end() - 16128); + } + } + video_.set_character_rom(roms.find(character)->second); + + // Set up the default memory blocks. On a II or II+ these values will never change. + // On a IIe they'll be affected by selection of auxiliary RAM. + set_paging(); + + // Set the whole card area to initially backed by nothing. + page(0xc0, 0xd0, nullptr, nullptr); + + insert_media(target.media); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + video_.set_scan_target(scan_target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return video_.get_scaled_scan_status(); + } + + /// Sets the type of display. + void set_display_type(Outputs::Display::DisplayType display_type) final { + video_.set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return video_.get_display_type(); + } + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) { + ++ cycles_since_video_update_; + ++ cycles_since_card_update_; + cycles_since_audio_update_ += Cycles(7); + + // The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched + // by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after + // 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary + // signal approximation that produces colour needs to be in phase, so a stretch of exactly + // 0.5 further colour cycles is added. The video class handles that implicitly, but it + // needs to be accumulated here for the audio. + cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65; + const bool is_stretched_cycle = !cycles_into_current_line_; + if(is_stretched_cycle) { + ++ cycles_since_audio_update_; + ++ stretched_cycles_since_card_update_; + } + + bool has_updated_cards = false; + if(read_pages_[address >> 8]) { + if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff]; + else { + if(address >= 0x200 && address < 0x6000) update_video(); + if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; + } + + if(is_iie(model)) { + auxiliary_switches_.access(address, isReadOperation(operation)); + } + } else { + // Assume a vapour read unless it turns out otherwise; this is a little + // wasteful but works for now. + // + // Longer version: like many other machines, when the Apple II reads from + // an address at which no hardware loads the data bus, through a process of + // practical analogue effects it'll end up receiving whatever was last on + // the bus. Which will always be whatever the video circuit fetched because + // that fetches in between every instruction. + // + // So this code assumes that'll happen unless it later determines that it + // doesn't. The call into the video isn't free because it's a just-in-time + // actor, but this will actually be the result most of the time so it's not + // too terrible. + if(isReadOperation(operation) && address != 0xc000) { + // Ensure any enqueued video changes are applied before grabbing the + // vapour value. + if(video_.has_deferred_actions()) { + update_video(); + } + *value = video_.get_last_read_value(cycles_since_video_update_); + } + + switch(address) { + default: + if(isReadOperation(operation)) { + // Read-only switches. + switch(address) { + default: break; + + case 0xc000: + *value = keyboard_.get_keyboard_input(); + break; + case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007: + case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f: + *value = (*value & 0x80) | (keyboard_.get_keyboard_input() & 0x7f); + break; + + case 0xc061: // Switch input 0. + *value &= 0x7f; + if( + joysticks_.button(0) || + (is_iie(model) && keyboard_.open_apple_is_pressed) + ) + *value |= 0x80; + break; + case 0xc062: // Switch input 1. + *value &= 0x7f; + if( + joysticks_.button(1) || + (is_iie(model) && keyboard_.closed_apple_is_pressed) + ) + *value |= 0x80; + break; + case 0xc063: // Switch input 2. + *value &= 0x7f; + if(joysticks_.button(2)) + *value |= 0x80; + break; + + case 0xc064: // Analogue input 0. + case 0xc065: // Analogue input 1. + case 0xc066: // Analogue input 2. + case 0xc067: { // Analogue input 3. + const size_t input = address - 0xc064; + *value &= 0x7f; + if(!joysticks_.analogue_channel_is_discharged(input)) { + *value |= 0x80; + } + } break; + + // The IIe-only state reads follow... +#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie(model)) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); + case 0xc011: IIeSwitchRead(language_card_.state().bank2); break; + case 0xc012: IIeSwitchRead(language_card_.state().read); break; + case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break; + case 0xc014: IIeSwitchRead(auxiliary_switches_.switches().write_auxiliary_memory); break; + case 0xc015: IIeSwitchRead(auxiliary_switches_.switches().internal_CX_rom); break; + case 0xc016: IIeSwitchRead(auxiliary_switches_.switches().alternative_zero_page); break; + case 0xc017: IIeSwitchRead(auxiliary_switches_.switches().slot_C3_rom); break; + case 0xc018: IIeSwitchRead(video_.get_80_store()); break; + case 0xc019: IIeSwitchRead(video_.get_is_vertical_blank(cycles_since_video_update_)); break; + case 0xc01a: IIeSwitchRead(video_.get_text()); break; + case 0xc01b: IIeSwitchRead(video_.get_mixed()); break; + case 0xc01c: IIeSwitchRead(video_.get_page2()); break; + case 0xc01d: IIeSwitchRead(video_.get_high_resolution()); break; + case 0xc01e: IIeSwitchRead(video_.get_alternative_character_set()); break; + case 0xc01f: IIeSwitchRead(video_.get_80_columns()); break; +#undef IIeSwitchRead + + case 0xc07f: + if(is_iie(model)) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00); + break; + } + } else { + // Write-only switches. All IIe as currently implemented. + if(is_iie(model)) { + auxiliary_switches_.access(address, false); + switch(address) { + default: break; + + case 0xc000: + case 0xc001: + update_video(); + video_.set_80_store(address&1); + break; + + case 0xc00c: + case 0xc00d: + update_video(); + video_.set_80_columns(address&1); + break; + + case 0xc00e: + case 0xc00f: + update_video(); + video_.set_alternative_character_set(address&1); + break; + } + } + } + break; + + case 0xc070: joysticks_.access_c070(); break; + + /* Switches triggered by reading or writing. */ + case 0xc050: + case 0xc051: + update_video(); + video_.set_text(address&1); + break; + case 0xc052: update_video(); video_.set_mixed(false); break; + case 0xc053: update_video(); video_.set_mixed(true); break; + case 0xc054: + case 0xc055: + update_video(); + video_.set_page2(address&1); + auxiliary_switches_.access(address, isReadOperation(operation)); + break; + case 0xc056: + case 0xc057: + update_video(); + video_.set_high_resolution(address&1); + auxiliary_switches_.access(address, isReadOperation(operation)); + break; + + case 0xc05e: + case 0xc05f: + if(is_iie(model)) { + update_video(); + video_.set_annunciator_3(!(address&1)); + } + break; + + case 0xc010: + keyboard_.clear_keyboard_input(); + + // On the IIe, reading C010 returns additional key info. + if(is_iie(model) && isReadOperation(operation)) { + *value = (keyboard_.get_key_is_down() ? 0x80 : 0x00) | (keyboard_.get_keyboard_input() & 0x7f); + } + break; + + case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037: + case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f: + update_audio(); + audio_toggle_.set_output(!audio_toggle_.get_output()); + break; + + case 0xc080: case 0xc084: case 0xc088: case 0xc08c: + case 0xc081: case 0xc085: case 0xc089: case 0xc08d: + case 0xc082: case 0xc086: case 0xc08a: case 0xc08e: + case 0xc083: case 0xc087: case 0xc08b: case 0xc08f: + language_card_.access(address, isReadOperation(operation)); + break; + } + + /* + Communication with cards follows. + */ + + if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xd000) { + // If this is a card access, figure out which card is at play before determining + // the totality of who needs messaging. + size_t card_number = 0; + Apple::II::Card::Select select = Apple::II::Card::None; + + if(address >= 0xc800) { + /* + Decode the 2kb area used for additional ROMs. + This is shared by all cards. + */ + card_number = active_card_; + select = Apple::II::Card::C8Region; + + // An access to $cfff will disable the active card. + if(address == 0xcfff) { + active_card_ = NoActiveCard; + } + } else if(address >= 0xc100) { + /* + Decode the area conventionally used by cards for ROMs: + 0xCn00 to 0xCnff: card n. + + This also sets the active card for the C8 region. + */ + active_card_ = card_number = (address - 0xc100) >> 8; + select = Apple::II::Card::IO; + } else { + /* + Decode the area conventionally used by cards for registers: + C0n0 to C0nF: card n - 8. + */ + card_number = (address - 0xc090) >> 4; + select = Apple::II::Card::Device; + } + + // If the selected card is a just-in-time card, update the just-in-time cards, + // and then message it specifically. + const bool is_read = isReadOperation(operation); + Apple::II::Card *const target = cards_[size_t(card_number)].get(); + if(target && !is_every_cycle_card(target)) { + update_just_in_time_cards(); + target->perform_bus_operation(select, is_read, address, value); + } + + // Update all the every-cycle cards regardless, but send them a ::None select if they're + // not the one actually selected. + for(const auto &card: every_cycle_cards_) { + card->run_for(Cycles(1), is_stretched_cycle); + card->perform_bus_operation( + (card == target) ? select : Apple::II::Card::None, + is_read, address, value); + } + has_updated_cards = true; + } + } + + if(!has_updated_cards && !every_cycle_cards_.empty()) { + // Update all every-cycle cards and give them the cycle. + const bool is_read = isReadOperation(operation); + for(const auto &card: every_cycle_cards_) { + card->run_for(Cycles(1), is_stretched_cycle); + card->perform_bus_operation(Apple::II::Card::None, is_read, address, value); + } + } + + // Update the card lists if any mutations are due. + if(card_lists_are_dirty_) { + card_lists_are_dirty_ = false; + + // There's only one counter of time since update + // for just-in-time cards. If something new is + // transitioning, that needs to be zeroed. + if(card_became_just_in_time_) { + card_became_just_in_time_ = false; + update_just_in_time_cards(); + } + + // Clear the two lists and repopulate. + every_cycle_cards_.clear(); + just_in_time_cards_.clear(); + for(const auto &card: cards_) { + if(!card) continue; + if(is_every_cycle_card(card.get())) { + every_cycle_cards_.push_back(card.get()); + } else { + just_in_time_cards_.push_back(card.get()); + } + } + } + + // Update analogue charge level. + joysticks_.update_charge(); + + return Cycles(1); + } + + void flush_output(int outputs) final { + update_just_in_time_cards(); + + if(outputs & Output::Video) { + update_video(); + } + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); + } + } + + void run_for(const Cycles cycles) final { + m6502_.run_for(cycles); + } + + bool prefers_logical_input() final { + return is_iie(model); + } + + Inputs::Keyboard &get_keyboard() final { + return keyboard_; + } + + void type_string(const std::string &string) final { + keyboard_.set_string_serialiser(std::make_unique(string, true)); + } + + bool can_type(char c) const final { + // Make an effort to type the entire printable ASCII range. + return c >= 32 && c < 127; + } + + // MARK:: Configuration options. + std::unique_ptr get_options() const final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->use_square_pixels = video_.get_use_square_pixels(); + return options; + } + + void set_options(const std::unique_ptr &str) { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); + video_.set_use_square_pixels(options->use_square_pixels); + } + + // MARK: MediaTarget + bool insert_media(const Analyser::Static::Media &media) final { + if(!media.disks.empty()) { + auto diskii = diskii_card(); + if(diskii) diskii->set_disk(media.disks[0], 0); + } + + if(!media.mass_storage_devices.empty()) { + auto scsi = scsi_card(); + if(scsi) scsi->set_storage_device(media.mass_storage_devices[0]); + } + + return true; + } + + // MARK: Activity::Source + void set_activity_observer(Activity::Observer *observer) final { + for(const auto &card: cards_) { + if(card) card->set_activity_observer(observer); + } + } + + // MARK: JoystickMachine + const std::vector> &get_joysticks() final { + return joysticks_.get_joysticks(); + } }; } diff --git a/Machines/Apple/AppleII/DiskIICard.hpp b/Machines/Apple/AppleII/DiskIICard.hpp index 4048dc6b4..be4bbfbad 100644 --- a/Machines/Apple/AppleII/DiskIICard.hpp +++ b/Machines/Apple/AppleII/DiskIICard.hpp @@ -22,23 +22,23 @@ namespace Apple::II { class DiskIICard: public Card, public ClockingHint::Observer { - public: - static ROM::Request rom_request(bool is_16_sector); - DiskIICard(ROM::Map &, bool is_16_sector); +public: + static ROM::Request rom_request(bool is_16_sector); + DiskIICard(ROM::Map &, bool is_16_sector); - void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final; - void run_for(Cycles cycles, int stretches) final; + void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final; + void run_for(Cycles cycles, int stretches) final; - void set_activity_observer(Activity::Observer *observer) final; + void set_activity_observer(Activity::Observer *observer) final; - void set_disk(const std::shared_ptr &disk, int drive); - Storage::Disk::Drive &get_drive(int drive); + void set_disk(const std::shared_ptr &disk, int drive); + Storage::Disk::Drive &get_drive(int drive); - private: - void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final; - std::vector boot_; - Apple::DiskII diskii_; - ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; +private: + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final; + std::vector boot_; + Apple::DiskII diskii_; + ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; }; } diff --git a/Machines/Apple/AppleII/Joystick.hpp b/Machines/Apple/AppleII/Joystick.hpp index 0f623377c..4a53fde1e 100644 --- a/Machines/Apple/AppleII/Joystick.hpp +++ b/Machines/Apple/AppleII/Joystick.hpp @@ -16,92 +16,92 @@ namespace Apple::II { class JoystickPair { - public: - JoystickPair() { - // Add a couple of joysticks. - joysticks_.emplace_back(new Joystick); - joysticks_.emplace_back(new Joystick); - } +public: + JoystickPair() { + // Add a couple of joysticks. + joysticks_.emplace_back(new Joystick); + joysticks_.emplace_back(new Joystick); + } - class Joystick: public Inputs::ConcreteJoystick { - public: - Joystick() : - ConcreteJoystick({ - Input(Input::Horizontal), - Input(Input::Vertical), + class Joystick: public Inputs::ConcreteJoystick { + public: + Joystick() : + ConcreteJoystick({ + Input(Input::Horizontal), + Input(Input::Vertical), - // The Apple II offers three buttons between two joysticks; - // this emulator puts three buttons on each joystick and - // combines them. - Input(Input::Fire, 0), - Input(Input::Fire, 1), - Input(Input::Fire, 2), - }) {} + // The Apple II offers three buttons between two joysticks; + // this emulator puts three buttons on each joystick and + // combines them. + Input(Input::Fire, 0), + Input(Input::Fire, 1), + Input(Input::Fire, 2), + }) {} - void did_set_input(const Input &input, float value) final { - if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical)) - axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value; - } - - void did_set_input(const Input &input, bool value) final { - if(input.type == Input::Type::Fire && input.info.control.index < 3) { - buttons[input.info.control.index] = value; - } - } - - bool buttons[3] = {false, false, false}; - float axes[2] = {0.5f, 0.5f}; - }; - - inline bool button(size_t index) { - return joystick(0)->buttons[index] || joystick(1)->buttons[2-index]; - } - - inline bool analogue_channel_is_discharged(size_t channel) { - return (1.0f - static_cast(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel]; - } - - inline void update_charge(float one_mhz_cycles = 1.0f) { - analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f); - } - - inline void access_c070() { - // Permit analogue inputs that are currently discharged to begin a charge cycle. - // Ensure those that were still charging retain that state. - for(size_t c = 0; c < 4; ++c) { - if(analogue_channel_is_discharged(c)) { - analogue_biases_[c] = 0.0f; - } else { - analogue_biases_[c] += analogue_charge_; + void did_set_input(const Input &input, float value) final { + if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical)) + axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value; } + + void did_set_input(const Input &input, bool value) final { + if(input.type == Input::Type::Fire && input.info.control.index < 3) { + buttons[input.info.control.index] = value; + } + } + + bool buttons[3] = {false, false, false}; + float axes[2] = {0.5f, 0.5f}; + }; + + inline bool button(size_t index) { + return joystick(0)->buttons[index] || joystick(1)->buttons[2-index]; + } + + inline bool analogue_channel_is_discharged(size_t channel) { + return (1.0f - static_cast(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel]; + } + + inline void update_charge(float one_mhz_cycles = 1.0f) { + analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f); + } + + inline void access_c070() { + // Permit analogue inputs that are currently discharged to begin a charge cycle. + // Ensure those that were still charging retain that state. + for(size_t c = 0; c < 4; ++c) { + if(analogue_channel_is_discharged(c)) { + analogue_biases_[c] = 0.0f; + } else { + analogue_biases_[c] += analogue_charge_; } - analogue_charge_ = 0.0f; } + analogue_charge_ = 0.0f; + } - const std::vector> &get_joysticks() { - return joysticks_; - } + const std::vector> &get_joysticks() { + return joysticks_; + } - private: - // On an Apple II, the programmer strobes 0xc070 and that causes each analogue input - // to begin a charge and discharge cycle **if they are not already charging**. - // The greater the analogue input, the faster they will charge and therefore the sooner - // they will discharge. - // - // This emulator models that with analogue_charge_ being essentially the amount of time, - // in charge threshold units, since 0xc070 was last strobed. But if any of the analogue - // inputs were already partially charged then they gain a bias in analogue_biases_. - // - // It's a little indirect, but it means only having to increment the one value in the - // main loop. - float analogue_charge_ = 0.0f; - float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f}; +private: + // On an Apple II, the programmer strobes 0xc070 and that causes each analogue input + // to begin a charge and discharge cycle **if they are not already charging**. + // The greater the analogue input, the faster they will charge and therefore the sooner + // they will discharge. + // + // This emulator models that with analogue_charge_ being essentially the amount of time, + // in charge threshold units, since 0xc070 was last strobed. But if any of the analogue + // inputs were already partially charged then they gain a bias in analogue_biases_. + // + // It's a little indirect, but it means only having to increment the one value in the + // main loop. + float analogue_charge_ = 0.0f; + float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - std::vector> joysticks_; + std::vector> joysticks_; - inline Joystick *joystick(size_t index) { - return static_cast(joysticks_[index].get()); - } + inline Joystick *joystick(size_t index) { + return static_cast(joysticks_[index].get()); + } }; } diff --git a/Machines/Apple/AppleII/LanguageCardSwitches.hpp b/Machines/Apple/AppleII/LanguageCardSwitches.hpp index 12441d50f..d98b8da5a 100644 --- a/Machines/Apple/AppleII/LanguageCardSwitches.hpp +++ b/Machines/Apple/AppleII/LanguageCardSwitches.hpp @@ -19,95 +19,95 @@ namespace Apple::II { * machine.set_language_card_paging() if the proper mapped state changes. */ template class LanguageCardSwitches { - public: - struct State { - /// When RAM is visible in the range $D000–$FFFF: - /// @c true indicates that bank 2 should be used between $D000 and $DFFF; - /// @c false indicates bank 1. - bool bank2 = true; +public: + struct State { + /// When RAM is visible in the range $D000–$FFFF: + /// @c true indicates that bank 2 should be used between $D000 and $DFFF; + /// @c false indicates bank 1. + bool bank2 = true; - /// @c true indicates that RAM should be readable in the range $D000–$FFFF; - /// @c false indicates ROM should be readable. - bool read = false; + /// @c true indicates that RAM should be readable in the range $D000–$FFFF; + /// @c false indicates ROM should be readable. + bool read = false; - /// @c true indicates that ROM is selected for 'writing' in the range $D000–$FFFF (i.e. writes are a no-op); - /// @c false indicates that RAM is selected for writing. - bool write = false; + /// @c true indicates that ROM is selected for 'writing' in the range $D000–$FFFF (i.e. writes are a no-op); + /// @c false indicates that RAM is selected for writing. + bool write = false; - bool operator != (const State &rhs) const { - return - bank2 != rhs.bank2 || - read != rhs.read || - write != rhs.write; - } - }; - - LanguageCardSwitches(Machine &machine) : machine_(machine) {} - - /// Used by an owner to forward any access to $c08x. - void access(uint16_t address, bool is_read) { - const auto previous_state = state_; - - // Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29. - - // "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1. - state_.bank2 = !(address & 8); - - // "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop" - // (other accesses reset it) - state_.read = !(((address&2) >> 1) ^ (address&1)); - - // "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set." - if(pre_write_ && is_read && (address&1)) state_.write = false; - - // "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range." - if(!(address&1)) state_.write = true; - - // ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.") - - // "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." - pre_write_ = is_read ? (address&1) : false; - - // Apply whatever the net effect of all that is to the memory map. - if(previous_state != state_) { - machine_.template set_paging(); - } - } - - /// Provides read-only access to the current language card switch state. - const State &state() const { - return state_; - } - - /// Provides relevant parts of the IIgs interface. - void set_state(uint8_t value) { - const auto previous_state = state_; - - // Bit 3: 1 => enable ROM, 0 => enable RAM. - state_.read = !(value & 0x08); - // Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference - // correcting the original, which lists them the other way around] - state_.bank2 = value & 0x04; - - if(previous_state != state_) { - machine_.template set_paging(); - } - } - - uint8_t get_state() const { + bool operator != (const State &rhs) const { return - (state_.read ? 0x00 : 0x08) | - (state_.bank2 ? 0x04 : 0x00); + bank2 != rhs.bank2 || + read != rhs.read || + write != rhs.write; } + }; - private: - Machine &machine_; - State state_; + LanguageCardSwitches(Machine &machine) : machine_(machine) {} - // This is an additional flip flop contained on the language card, but - // it is one step removed from current banking state, so I've excluded it - // from the State struct. - bool pre_write_ = false; + /// Used by an owner to forward any access to $c08x. + void access(uint16_t address, bool is_read) { + const auto previous_state = state_; + + // Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29. + + // "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1. + state_.bank2 = !(address & 8); + + // "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop" + // (other accesses reset it) + state_.read = !(((address&2) >> 1) ^ (address&1)); + + // "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set." + if(pre_write_ && is_read && (address&1)) state_.write = false; + + // "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range." + if(!(address&1)) state_.write = true; + + // ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.") + + // "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access." + pre_write_ = is_read ? (address&1) : false; + + // Apply whatever the net effect of all that is to the memory map. + if(previous_state != state_) { + machine_.template set_paging(); + } + } + + /// Provides read-only access to the current language card switch state. + const State &state() const { + return state_; + } + + /// Provides relevant parts of the IIgs interface. + void set_state(uint8_t value) { + const auto previous_state = state_; + + // Bit 3: 1 => enable ROM, 0 => enable RAM. + state_.read = !(value & 0x08); + // Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference + // correcting the original, which lists them the other way around] + state_.bank2 = value & 0x04; + + if(previous_state != state_) { + machine_.template set_paging(); + } + } + + uint8_t get_state() const { + return + (state_.read ? 0x00 : 0x08) | + (state_.bank2 ? 0x04 : 0x00); + } + +private: + Machine &machine_; + State state_; + + // This is an additional flip flop contained on the language card, but + // it is one step removed from current banking state, so I've excluded it + // from the State struct. + bool pre_write_ = false; }; } diff --git a/Machines/Apple/AppleII/Mockingboard.hpp b/Machines/Apple/AppleII/Mockingboard.hpp index bef7e9f5e..306e9f202 100644 --- a/Machines/Apple/AppleII/Mockingboard.hpp +++ b/Machines/Apple/AppleII/Mockingboard.hpp @@ -15,121 +15,121 @@ namespace Apple::II { class AYPair { - public: - AYPair(Concurrency::AsyncTaskQueue &queue) : - ays_{ - {GI::AY38910::Personality::AY38910, queue}, - {GI::AY38910::Personality::AY38910, queue}, - } {} +public: + AYPair(Concurrency::AsyncTaskQueue &queue) : + ays_{ + {GI::AY38910::Personality::AY38910, queue}, + {GI::AY38910::Personality::AY38910, queue}, + } {} - void advance() { - ays_[0].advance(); - ays_[1].advance(); - } + void advance() { + ays_[0].advance(); + ays_[1].advance(); + } - void set_sample_volume_range(std::int16_t range) { - ays_[0].set_sample_volume_range(range); - ays_[1].set_sample_volume_range(range); - } + void set_sample_volume_range(std::int16_t range) { + ays_[0].set_sample_volume_range(range); + ays_[1].set_sample_volume_range(range); + } - bool is_zero_level() const { - return ays_[0].is_zero_level() && ays_[1].is_zero_level(); - } + bool is_zero_level() const { + return ays_[0].is_zero_level() && ays_[1].is_zero_level(); + } - Outputs::Speaker::StereoSample level() const { - Outputs::Speaker::StereoSample level; - level.left = ays_[0].level(); - level.right = ays_[1].level(); - return level; - } + Outputs::Speaker::StereoSample level() const { + Outputs::Speaker::StereoSample level; + level.left = ays_[0].level(); + level.right = ays_[1].level(); + return level; + } - GI::AY38910::AY38910SampleSource &get(int index) { - return ays_[index]; - } + GI::AY38910::AY38910SampleSource &get(int index) { + return ays_[index]; + } - private: - GI::AY38910::AY38910SampleSource ays_[2]; +private: + GI::AY38910::AY38910SampleSource ays_[2]; }; class Mockingboard: public Card { - public: - Mockingboard(AYPair &ays) : - vias_{ {handlers_[0]}, {handlers_[1]} }, - handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} { - set_select_constraints(0); +public: + Mockingboard(AYPair &ays) : + vias_{ {handlers_[0]}, {handlers_[1]} }, + handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} { + set_select_constraints(0); + } + + void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final { + if(!(select & IO)) { + return; } - void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final { - if(!(select & IO)) { - return; - } + int index = (address >> 7) & 1; + if(is_read) { + *value = vias_[index].read(address); + } else { + vias_[index].write(address, *value); + } + } - int index = (address >> 7) & 1; - if(is_read) { - *value = vias_[index].read(address); + void run_for(Cycles cycles, int) final { + vias_[0].run_for(cycles); + vias_[1].run_for(cycles); + } + + bool nmi() final { + return handlers_[1].interrupt; + } + + bool irq() final { + return handlers_[0].interrupt; + } + + void did_change_interrupt_flags() { + delegate_->card_did_change_interrupt_flags(this); + } + +private: + struct AYVIA: public MOS::MOS6522::PortHandler { + AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource &ay) : + card(card), ay(ay) {} + + void set_interrupt_status(bool status) { + interrupt = status; + card.did_change_interrupt_flags(); + } + + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { + if(port) { + using ControlLines = GI::AY38910::ControlLines; + ay.set_control_lines( + ControlLines( + ((value & 1) ? ControlLines::BC1 : 0) | + ((value & 2) ? ControlLines::BDIR : 0) | + ControlLines::BC2 + ) + ); + + ay.set_reset(!(value & 4)); } else { - vias_[index].write(address, *value); + ay.set_data_input(value); } } - void run_for(Cycles cycles, int) final { - vias_[0].run_for(cycles); - vias_[1].run_for(cycles); - } - - bool nmi() final { - return handlers_[1].interrupt; - } - - bool irq() final { - return handlers_[0].interrupt; - } - - void did_change_interrupt_flags() { - delegate_->card_did_change_interrupt_flags(this); - } - - private: - struct AYVIA: public MOS::MOS6522::PortHandler { - AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource &ay) : - card(card), ay(ay) {} - - void set_interrupt_status(bool status) { - interrupt = status; - card.did_change_interrupt_flags(); + uint8_t get_port_input(MOS::MOS6522::Port port) { + if(!port) { + return ay.get_data_output(); } + return 0xff; + } - void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { - if(port) { - using ControlLines = GI::AY38910::ControlLines; - ay.set_control_lines( - ControlLines( - ((value & 1) ? ControlLines::BC1 : 0) | - ((value & 2) ? ControlLines::BDIR : 0) | - ControlLines::BC2 - ) - ); + bool interrupt; + Mockingboard &card; + GI::AY38910::AY38910SampleSource &ay; + }; - ay.set_reset(!(value & 4)); - } else { - ay.set_data_input(value); - } - } - - uint8_t get_port_input(MOS::MOS6522::Port port) { - if(!port) { - return ay.get_data_output(); - } - return 0xff; - } - - bool interrupt; - Mockingboard &card; - GI::AY38910::AY38910SampleSource &ay; - }; - - MOS::MOS6522::MOS6522 vias_[2]; - AYVIA handlers_[2]; + MOS::MOS6522::MOS6522 vias_[2]; + AYVIA handlers_[2]; }; } diff --git a/Machines/Apple/AppleII/SCSICard.hpp b/Machines/Apple/AppleII/SCSICard.hpp index 18f7182e4..e1b15cc79 100644 --- a/Machines/Apple/AppleII/SCSICard.hpp +++ b/Machines/Apple/AppleII/SCSICard.hpp @@ -25,31 +25,31 @@ namespace Apple::II { class SCSICard: public Card { - public: - static ROM::Request rom_request(); - SCSICard(ROM::Map &, int clock_rate); +public: + static ROM::Request rom_request(); + SCSICard(ROM::Map &, int clock_rate); - void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final; + void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final; - void set_storage_device(const std::shared_ptr &device); + void set_storage_device(const std::shared_ptr &device); - void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final { - scsi_bus_.run_for(cycles); - } + void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) final { + scsi_bus_.run_for(cycles); + } - void set_activity_observer(Activity::Observer *observer) final; + void set_activity_observer(Activity::Observer *observer) final; - private: - uint8_t *ram_pointer_ = nullptr; - uint8_t *rom_pointer_ = nullptr; +private: + uint8_t *ram_pointer_ = nullptr; + uint8_t *rom_pointer_ = nullptr; - std::array ram_; - std::array rom_; + std::array ram_; + std::array rom_; - SCSI::Bus scsi_bus_; - NCR::NCR5380::NCR5380 ncr5380_; - SCSI::Target::Target storage_; - Log::Logger logger_; + SCSI::Bus scsi_bus_; + NCR::NCR5380::NCR5380 ncr5380_; + SCSI::Target::Target storage_; + Log::Logger logger_; }; } diff --git a/Machines/Apple/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp index f0a8e9055..18af022c9 100644 --- a/Machines/Apple/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -116,398 +116,398 @@ class VideoBase: public VideoSwitches { }; template class Video: public VideoBase { - public: - /// Constructs an instance of the video feed; a CRT is also created. - Video(BusHandler &bus_handler) : - VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }), - bus_handler_(bus_handler) {} +public: + /// Constructs an instance of the video feed; a CRT is also created. + Video(BusHandler &bus_handler) : + VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }), + bus_handler_(bus_handler) {} - /*! - Obtains the last value the video read prior to time now+offset, according to the *current* - video mode, i.e. not allowing for any changes still enqueued. - */ - uint8_t get_last_read_value(Cycles offset) { - // Rules of generation: + /*! + Obtains the last value the video read prior to time now+offset, according to the *current* + video mode, i.e. not allowing for any changes still enqueued. + */ + uint8_t get_last_read_value(Cycles offset) { + // Rules of generation: - // FOR ALL MODELS IN ALL MODES: - // - // - "Screen memory is divided into 128-byte segments. Each segment is divided into the FIRST 40, the - // SECOND 40, the THIRD 40, and eight bytes of no man's memory (UNUSED 8)." (5-8*) - // - // - "The VBL base addresses are equal to the FIRST 40 base addresses minus eight bytes using 128-byte - // wraparound subtraction. Example: $400 minus $8 gives $478; not $3F8." (5-11*) - // - // - "The memory locations scanned during HBL prior to a displayed line are the 24 bytes just below the - // displayed area, using 128-byte wraparound addressing." (5-13*) - // - // - "The first address of HBL is always addressed twice consecutively" (5-11*) - // - // - "Memory scanned by lines 256 through 261 is identical to memory scanned by lines 250 through 255, - // so those six 64-byte sections are scanned twice" (5-13*) + // FOR ALL MODELS IN ALL MODES: + // + // - "Screen memory is divided into 128-byte segments. Each segment is divided into the FIRST 40, the + // SECOND 40, the THIRD 40, and eight bytes of no man's memory (UNUSED 8)." (5-8*) + // + // - "The VBL base addresses are equal to the FIRST 40 base addresses minus eight bytes using 128-byte + // wraparound subtraction. Example: $400 minus $8 gives $478; not $3F8." (5-11*) + // + // - "The memory locations scanned during HBL prior to a displayed line are the 24 bytes just below the + // displayed area, using 128-byte wraparound addressing." (5-13*) + // + // - "The first address of HBL is always addressed twice consecutively" (5-11*) + // + // - "Memory scanned by lines 256 through 261 is identical to memory scanned by lines 250 through 255, + // so those six 64-byte sections are scanned twice" (5-13*) - // FOR II AND II+ ONLY (NOT IIE OR LATER) IN TEXT/LORES MODE ONLY (NOT HIRES): - // - // - "HBL scanned memory begins $18 bytes before display scanned memory plus $1000." (5-11*) - // - // - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned - // before address $400 is $47F during VBL. The address scanned before $400 when VBL is false is - // $147F." (5-11*) - // - // - "the memory scanned during HBL is completely separate from the memory scanned during HBL´." (5-11*) - // - // - "HBL scanned memory is in an area normally taken up by Applesoft programs or Integer BASIC - // variables" (5-37*) - // - // - Figure 5.17 Screen Memory Scanning (5-37*) + // FOR II AND II+ ONLY (NOT IIE OR LATER) IN TEXT/LORES MODE ONLY (NOT HIRES): + // + // - "HBL scanned memory begins $18 bytes before display scanned memory plus $1000." (5-11*) + // + // - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned + // before address $400 is $47F during VBL. The address scanned before $400 when VBL is false is + // $147F." (5-11*) + // + // - "the memory scanned during HBL is completely separate from the memory scanned during HBL´." (5-11*) + // + // - "HBL scanned memory is in an area normally taken up by Applesoft programs or Integer BASIC + // variables" (5-37*) + // + // - Figure 5.17 Screen Memory Scanning (5-37*) - // FOR IIE AND LATER IN ALL MODES AND II AND II+ IN HIRES MODE: - // - // - "HBL scanned memory begins $18 bytes before display scanned memory." (5-10**) - // - // - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned - // before address $400 is $47F." (5-11**) - // - // - "during HBL, the memory locations that are scanned are in the displayed memory area." (5-13*) - // - // - "Programs written for the Apple II may well not perform correctly on the Apple IIe because of - // differences in scanning during HBL. In the Apple II, HBL scanned memory was separate from other - // display memory in TEXT/LORES scanning. In the Apple IIe, HBL scanned memory overlaps other scanned - // memory in TEXT/LORES scanning in similar fashion to HIRES scanning." (5-43**) - // - // - Figure 5.17 Display Memory Scanning (5-41**) + // FOR IIE AND LATER IN ALL MODES AND II AND II+ IN HIRES MODE: + // + // - "HBL scanned memory begins $18 bytes before display scanned memory." (5-10**) + // + // - "Horizontal scanning wraps around at the 128-byte segment boundaries. Example: the address scanned + // before address $400 is $47F." (5-11**) + // + // - "during HBL, the memory locations that are scanned are in the displayed memory area." (5-13*) + // + // - "Programs written for the Apple II may well not perform correctly on the Apple IIe because of + // differences in scanning during HBL. In the Apple II, HBL scanned memory was separate from other + // display memory in TEXT/LORES scanning. In the Apple IIe, HBL scanned memory overlaps other scanned + // memory in TEXT/LORES scanning in similar fashion to HIRES scanning." (5-43**) + // + // - Figure 5.17 Display Memory Scanning (5-41**) - // Source: * Understanding the Apple II by Jim Sather - // Source: ** Understanding the Apple IIe by Jim Sather + // Source: * Understanding the Apple II by Jim Sather + // Source: ** Understanding the Apple IIe by Jim Sather - // Determine column at offset. - int mapped_column = column_ + int(offset.as_integral()); + // Determine column at offset. + int mapped_column = column_ + int(offset.as_integral()); - // Map that backwards from the internal pixels-at-start generation to pixels-at-end - // (so what was column 0 is now column 25). - mapped_column += 25; + // Map that backwards from the internal pixels-at-start generation to pixels-at-end + // (so what was column 0 is now column 25). + mapped_column += 25; - // Apply carry into the row counter. - int mapped_row = row_ + (mapped_column / 65); - mapped_row %= 262; - mapped_column %= 65; + // Apply carry into the row counter. + int mapped_row = row_ + (mapped_column / 65); + mapped_row %= 262; + mapped_column %= 65; - // Remember if we're in a horizontal blanking interval. - int hbl = mapped_column < 25; + // Remember if we're in a horizontal blanking interval. + int hbl = mapped_column < 25; - // The first column is read twice. - if(mapped_column == 0) { - mapped_column = 1; - } - - // Vertical blanking rows read eight bytes earlier. - if(mapped_row >= 192) { - mapped_column -= 8; - } - - // Apple out-of-bounds row logic. - if(mapped_row >= 256) { - mapped_row = 0x3a + (mapped_row&255); - } else { - mapped_row %= 192; - } - - // Calculate the address. - uint16_t read_address = uint16_t(get_row_address(mapped_row) + mapped_column - 25); - - // Wraparound addressing within 128 byte sections. - if(mapped_row < 64 && mapped_column < 25) { - read_address += 128; - } - - if(hbl && !is_iie_) { - // On Apple II and II+ (not IIe or later) in text/lores mode (not hires), horizontal - // blanking bytes read from $1000 higher. - const GraphicsMode pixel_mode = graphics_mode(mapped_row); - if((pixel_mode == GraphicsMode::Text) || (pixel_mode == GraphicsMode::LowRes)) { - read_address += 0x1000; - } - } - - // Read the address and return the value. - uint8_t value, aux_value; - bus_handler_.perform_read(read_address, 1, &value, &aux_value); - return value; + // The first column is read twice. + if(mapped_column == 0) { + mapped_column = 1; } - /*! - @returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise. - */ - bool get_is_vertical_blank(Cycles offset) { - // Determine column at offset. - int mapped_column = column_ + int(offset.as_integral()); - - // Map that backwards from the internal pixels-at-start generation to pixels-at-end - // (so what was column 0 is now column 25). - mapped_column += 25; - - // Apply carry into the row counter. - int mapped_row = row_ + (mapped_column / 65); - mapped_row %= 262; - - // Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html - // "on the IIe, the screen is blanked when the bit is low". - return mapped_row < 192; + // Vertical blanking rows read eight bytes earlier. + if(mapped_row >= 192) { + mapped_column -= 8; } - private: - /*! - Advances time by @c cycles; expects to be fed by the CPU clock. - Implicitly adds an extra half a colour clock at the end of - line. + // Apple out-of-bounds row logic. + if(mapped_row >= 256) { + mapped_row = 0x3a + (mapped_row&255); + } else { + mapped_row %= 192; + } + + // Calculate the address. + uint16_t read_address = uint16_t(get_row_address(mapped_row) + mapped_column - 25); + + // Wraparound addressing within 128 byte sections. + if(mapped_row < 64 && mapped_column < 25) { + read_address += 128; + } + + if(hbl && !is_iie_) { + // On Apple II and II+ (not IIe or later) in text/lores mode (not hires), horizontal + // blanking bytes read from $1000 higher. + const GraphicsMode pixel_mode = graphics_mode(mapped_row); + if((pixel_mode == GraphicsMode::Text) || (pixel_mode == GraphicsMode::LowRes)) { + read_address += 0x1000; + } + } + + // Read the address and return the value. + uint8_t value, aux_value; + bus_handler_.perform_read(read_address, 1, &value, &aux_value); + return value; + } + + /*! + @returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise. + */ + bool get_is_vertical_blank(Cycles offset) { + // Determine column at offset. + int mapped_column = column_ + int(offset.as_integral()); + + // Map that backwards from the internal pixels-at-start generation to pixels-at-end + // (so what was column 0 is now column 25). + mapped_column += 25; + + // Apply carry into the row counter. + int mapped_row = row_ + (mapped_column / 65); + mapped_row %= 262; + + // Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html + // "on the IIe, the screen is blanked when the bit is low". + return mapped_row < 192; + } + +private: + /*! + Advances time by @c cycles; expects to be fed by the CPU clock. + Implicitly adds an extra half a colour clock at the end of + line. + */ + void advance(Cycles cycles) { + /* + Addressing scheme used throughout is that column 0 is the first column with pixels in it; + row 0 is the first row with pixels in it. + + A frame is oriented around 65 cycles across, 262 lines down. */ - void advance(Cycles cycles) { - /* - Addressing scheme used throughout is that column 0 is the first column with pixels in it; - row 0 is the first row with pixels in it. + constexpr int first_sync_line = 220; // A complete guess. Information needed. + constexpr int first_sync_column = 49; // Also a guess. + constexpr int sync_length = 4; // One of the two likely candidates. - A frame is oriented around 65 cycles across, 262 lines down. - */ - constexpr int first_sync_line = 220; // A complete guess. Information needed. - constexpr int first_sync_column = 49; // Also a guess. - constexpr int sync_length = 4; // One of the two likely candidates. + int int_cycles = int(cycles.as_integral()); + while(int_cycles) { + const int cycles_this_line = std::min(65 - column_, int_cycles); + const int ending_column = column_ + cycles_this_line; + const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3); - int int_cycles = int(cycles.as_integral()); - while(int_cycles) { - const int cycles_this_line = std::min(65 - column_, int_cycles); - const int ending_column = column_ + cycles_this_line; - const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3); - - if(is_vertical_sync_line) { - // In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising - // pulses (and hence keep hsync approximately where it should be during vsync). - const int blank_start = std::max(first_sync_column - sync_length, column_); - const int blank_end = std::min(first_sync_column, ending_column); - if(blank_end > blank_start) { - if(blank_start > column_) { - crt_.output_sync((blank_start - column_) * 14); - } - crt_.output_blank((blank_end - blank_start) * 14); - if(blank_end < ending_column) { - crt_.output_sync((ending_column - blank_end) * 14); - } - } else { - crt_.output_sync(cycles_this_line * 14); + if(is_vertical_sync_line) { + // In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising + // pulses (and hence keep hsync approximately where it should be during vsync). + const int blank_start = std::max(first_sync_column - sync_length, column_); + const int blank_end = std::min(first_sync_column, ending_column); + if(blank_end > blank_start) { + if(blank_start > column_) { + crt_.output_sync((blank_start - column_) * 14); + } + crt_.output_blank((blank_end - blank_start) * 14); + if(blank_end < ending_column) { + crt_.output_sync((ending_column - blank_end) * 14); } } else { - const GraphicsMode line_mode = graphics_mode(row_); + crt_.output_sync(cycles_this_line * 14); + } + } else { + const GraphicsMode line_mode = graphics_mode(row_); - // Determine whether there's any fetching to do. Fetching occurs during the first - // 40 columns of rows prior to 192. - if(row_ < 192 && column_ < 40) { - const int character_row = row_ >> 3; - const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7)); + // Determine whether there's any fetching to do. Fetching occurs during the first + // 40 columns of rows prior to 192. + if(row_ < 192 && column_ < 40) { + const int character_row = row_ >> 3; + const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7)); - // Grab the memory contents that'll be needed momentarily. - const int fetch_end = std::min(40, ending_column); - uint16_t fetch_address; - switch(line_mode) { - default: - case GraphicsMode::Text: - case GraphicsMode::DoubleText: - case GraphicsMode::LowRes: - case GraphicsMode::FatLowRes: - case GraphicsMode::DoubleLowRes: { - const uint16_t text_address = uint16_t(((video_page()+1) * 0x400) + row_address); - fetch_address = uint16_t(text_address + column_); - } break; + // Grab the memory contents that'll be needed momentarily. + const int fetch_end = std::min(40, ending_column); + uint16_t fetch_address; + switch(line_mode) { + default: + case GraphicsMode::Text: + case GraphicsMode::DoubleText: + case GraphicsMode::LowRes: + case GraphicsMode::FatLowRes: + case GraphicsMode::DoubleLowRes: { + const uint16_t text_address = uint16_t(((video_page()+1) * 0x400) + row_address); + fetch_address = uint16_t(text_address + column_); + } break; - case GraphicsMode::HighRes: - case GraphicsMode::DoubleHighRes: - fetch_address = uint16_t(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_); - break; - } - - bus_handler_.perform_read( - fetch_address, - size_t(fetch_end - column_), - &base_stream_[size_t(column_)], - &auxiliary_stream_[size_t(column_)]); + case GraphicsMode::HighRes: + case GraphicsMode::DoubleHighRes: + fetch_address = uint16_t(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_); + break; } - if(row_ < 192) { - // The pixel area is the first 40.5 columns; base contents - // remain where they would naturally be but auxiliary - // graphics appear to the left of that. - if(!column_) { - pixel_pointer_ = crt_.begin_data(568); - graphics_carry_ = 0; - was_double_ = true; + bus_handler_.perform_read( + fetch_address, + size_t(fetch_end - column_), + &base_stream_[size_t(column_)], + &auxiliary_stream_[size_t(column_)]); + } + + if(row_ < 192) { + // The pixel area is the first 40.5 columns; base contents + // remain where they would naturally be but auxiliary + // graphics appear to the left of that. + if(!column_) { + pixel_pointer_ = crt_.begin_data(568); + graphics_carry_ = 0; + was_double_ = true; + } + + if(column_ < 40) { + const int pixel_start = std::max(0, column_); + const int pixel_end = std::min(40, ending_column); + const int pixel_row = row_ & 7; + + const bool is_double = is_double_mode(line_mode); + if(!is_double && was_double_ && pixel_pointer_) { + pixel_pointer_[pixel_start*14 + 0] = + pixel_pointer_[pixel_start*14 + 1] = + pixel_pointer_[pixel_start*14 + 2] = + pixel_pointer_[pixel_start*14 + 3] = + pixel_pointer_[pixel_start*14 + 4] = + pixel_pointer_[pixel_start*14 + 5] = + pixel_pointer_[pixel_start*14 + 6] = 0; + } + was_double_ = is_double; + + if(pixel_pointer_) { + switch(line_mode) { + case GraphicsMode::Text: + output_text( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start), + size_t(pixel_row)); + break; + + case GraphicsMode::DoubleText: + output_double_text( + &pixel_pointer_[pixel_start * 14], + &base_stream_[size_t(pixel_start)], + &auxiliary_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start), + size_t(pixel_row)); + break; + + case GraphicsMode::LowRes: + output_low_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; + + case GraphicsMode::FatLowRes: + output_fat_low_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; + + case GraphicsMode::DoubleLowRes: + output_double_low_resolution( + &pixel_pointer_[pixel_start * 14], + &base_stream_[size_t(pixel_start)], + &auxiliary_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start), + pixel_start, + pixel_row); + break; + + case GraphicsMode::HighRes: + output_high_resolution( + &pixel_pointer_[pixel_start * 14 + 7], + &base_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start)); + break; + + case GraphicsMode::DoubleHighRes: + output_double_high_resolution( + &pixel_pointer_[pixel_start * 14], + &base_stream_[size_t(pixel_start)], + &auxiliary_stream_[size_t(pixel_start)], + size_t(pixel_end - pixel_start)); + break; + + default: break; + } } - if(column_ < 40) { - const int pixel_start = std::max(0, column_); - const int pixel_end = std::min(40, ending_column); - const int pixel_row = row_ & 7; - - const bool is_double = is_double_mode(line_mode); - if(!is_double && was_double_ && pixel_pointer_) { - pixel_pointer_[pixel_start*14 + 0] = - pixel_pointer_[pixel_start*14 + 1] = - pixel_pointer_[pixel_start*14 + 2] = - pixel_pointer_[pixel_start*14 + 3] = - pixel_pointer_[pixel_start*14 + 4] = - pixel_pointer_[pixel_start*14 + 5] = - pixel_pointer_[pixel_start*14 + 6] = 0; - } - was_double_ = is_double; - + if(pixel_end == 40) { if(pixel_pointer_) { - switch(line_mode) { - case GraphicsMode::Text: - output_text( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start), - size_t(pixel_row)); - break; - - case GraphicsMode::DoubleText: - output_double_text( - &pixel_pointer_[pixel_start * 14], - &base_stream_[size_t(pixel_start)], - &auxiliary_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start), - size_t(pixel_row)); - break; - - case GraphicsMode::LowRes: - output_low_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; - - case GraphicsMode::FatLowRes: - output_fat_low_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; - - case GraphicsMode::DoubleLowRes: - output_double_low_resolution( - &pixel_pointer_[pixel_start * 14], - &base_stream_[size_t(pixel_start)], - &auxiliary_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start), - pixel_start, - pixel_row); - break; - - case GraphicsMode::HighRes: - output_high_resolution( - &pixel_pointer_[pixel_start * 14 + 7], - &base_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start)); - break; - - case GraphicsMode::DoubleHighRes: - output_double_high_resolution( - &pixel_pointer_[pixel_start * 14], - &base_stream_[size_t(pixel_start)], - &auxiliary_stream_[size_t(pixel_start)], - size_t(pixel_end - pixel_start)); - break; - - default: break; + if(was_double_) { + pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] = + pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0; + } else { + if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) + pixel_pointer_[567] = graphics_carry_; + else + pixel_pointer_[567] = 0; } } - if(pixel_end == 40) { - if(pixel_pointer_) { - if(was_double_) { - pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] = - pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0; - } else { - if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80) - pixel_pointer_[567] = graphics_carry_; - else - pixel_pointer_[567] = 0; - } - } - - crt_.output_data(568, 568); - pixel_pointer_ = nullptr; - } - } - } else { - if(column_ < 40 && ending_column >= 40) { - crt_.output_blank(568); + crt_.output_data(568, 568); + pixel_pointer_ = nullptr; } } - - /* - The left border, sync, right border pattern doesn't depend on whether - there were pixels this row and is output as soon as it is known. - */ - - if(column_ < first_sync_column && ending_column >= first_sync_column) { - crt_.output_blank(first_sync_column*14 - 568); - } - - if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) { - crt_.output_sync(sync_length*14); - } - - int second_blank_start; - // Colour burst is present on all lines of the display if graphics mode is enabled on the top - // portion; therefore use the graphics mode on line 0 rather than the current line, to avoid - // disabling it in mixed modes. - if(!is_text_mode(graphics_mode(0))) { - const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); - const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); - if(colour_burst_end > colour_burst_start) { - // UGLY HACK AHOY! - // The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not. - // Supply the real phase value if this is an Apple build. - // TODO: eliminate UGLY HACK. -#if defined(__APPLE__) && !defined(IGNORE_APPLE) - constexpr uint8_t phase = 224; -#else - constexpr uint8_t phase = 192; -#endif - - crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase); - } - - second_blank_start = std::max(first_sync_column + sync_length + 3, column_); - } else { - second_blank_start = std::max(first_sync_column + sync_length, column_); - } - - if(ending_column > second_blank_start) { - crt_.output_blank((ending_column - second_blank_start) * 14); + } else { + if(column_ < 40 && ending_column >= 40) { + crt_.output_blank(568); } } - int_cycles -= cycles_this_line; - column_ = (column_ + cycles_this_line) % 65; - if(!column_) { - row_ = (row_ + 1) % 262; - did_end_line(); + /* + The left border, sync, right border pattern doesn't depend on whether + there were pixels this row and is output as soon as it is known. + */ - // Add an extra half a colour cycle of blank; this isn't counted in the run_for - // count explicitly but is promised. If this is a vertical sync line, output sync - // instead of blank, taking that to be the default level. - if(is_vertical_sync_line) { - crt_.output_sync(2); - } else { - crt_.output_blank(2); + if(column_ < first_sync_column && ending_column >= first_sync_column) { + crt_.output_blank(first_sync_column*14 - 568); + } + + if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) { + crt_.output_sync(sync_length*14); + } + + int second_blank_start; + // Colour burst is present on all lines of the display if graphics mode is enabled on the top + // portion; therefore use the graphics mode on line 0 rather than the current line, to avoid + // disabling it in mixed modes. + if(!is_text_mode(graphics_mode(0))) { + const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_); + const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column); + if(colour_burst_end > colour_burst_start) { + // UGLY HACK AHOY! + // The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not. + // Supply the real phase value if this is an Apple build. + // TODO: eliminate UGLY HACK. +#if defined(__APPLE__) && !defined(IGNORE_APPLE) + constexpr uint8_t phase = 224; +#else + constexpr uint8_t phase = 192; +#endif + + crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase); } + + second_blank_start = std::max(first_sync_column + sync_length + 3, column_); + } else { + second_blank_start = std::max(first_sync_column + sync_length, column_); + } + + if(ending_column > second_blank_start) { + crt_.output_blank((ending_column - second_blank_start) * 14); + } + } + + int_cycles -= cycles_this_line; + column_ = (column_ + cycles_this_line) % 65; + if(!column_) { + row_ = (row_ + 1) % 262; + did_end_line(); + + // Add an extra half a colour cycle of blank; this isn't counted in the run_for + // count explicitly but is promised. If this is a vertical sync line, output sync + // instead of blank, taking that to be the default level. + if(is_vertical_sync_line) { + crt_.output_sync(2); + } else { + crt_.output_blank(2); } } } + } - BusHandler &bus_handler_; + BusHandler &bus_handler_; }; } diff --git a/Machines/Apple/AppleII/VideoSwitches.hpp b/Machines/Apple/AppleII/VideoSwitches.hpp index 894c7df83..98fd69fa6 100644 --- a/Machines/Apple/AppleII/VideoSwitches.hpp +++ b/Machines/Apple/AppleII/VideoSwitches.hpp @@ -29,327 +29,327 @@ constexpr bool is_text_mode(const GraphicsMode m) { return m <= GraphicsMode::Do constexpr bool is_double_mode(const GraphicsMode m) { return int(m) & 1; } template class VideoSwitches { - public: - /*! - Constructs a new instance of VideoSwitches in which changes to relevant switches - affect the video mode only after @c delay cycles. +public: + /*! + Constructs a new instance of VideoSwitches in which changes to relevant switches + affect the video mode only after @c delay cycles. - If @c is_iie is true, these switches will set up the character zones for an IIe-esque - set of potential flashing characters and alternate video modes. - */ - VideoSwitches(bool is_iie, TimeUnit delay, std::function &&target) : delay_(delay), deferrer_(std::move(target)) { - character_zones_[0].xor_mask = 0xff; - character_zones_[0].address_mask = 0x3f; - character_zones_[1].xor_mask = 0; - character_zones_[1].address_mask = 0x3f; - character_zones_[2].xor_mask = 0; - character_zones_[2].address_mask = 0x3f; - character_zones_[3].xor_mask = 0; - character_zones_[3].address_mask = 0x3f; + If @c is_iie is true, these switches will set up the character zones for an IIe-esque + set of potential flashing characters and alternate video modes. + */ + VideoSwitches(bool is_iie, TimeUnit delay, std::function &&target) : delay_(delay), deferrer_(std::move(target)) { + character_zones_[0].xor_mask = 0xff; + character_zones_[0].address_mask = 0x3f; + character_zones_[1].xor_mask = 0; + character_zones_[1].address_mask = 0x3f; + character_zones_[2].xor_mask = 0; + character_zones_[2].address_mask = 0x3f; + character_zones_[3].xor_mask = 0; + character_zones_[3].address_mask = 0x3f; - if(is_iie) { - character_zones_[1].xor_mask = - character_zones_[2].xor_mask = - character_zones_[3].xor_mask = 0xff; - character_zones_[2].address_mask = - character_zones_[3].address_mask = 0xff; - } + if(is_iie) { + character_zones_[1].xor_mask = + character_zones_[2].xor_mask = + character_zones_[3].xor_mask = 0xff; + character_zones_[2].address_mask = + character_zones_[3].address_mask = 0xff; } + } - /*! - Advances @c cycles. - */ - void run_for(TimeUnit cycles) { - deferrer_.run_for(cycles); - } + /*! + Advances @c cycles. + */ + void run_for(TimeUnit cycles) { + deferrer_.run_for(cycles); + } - /* - Descriptions for the setters below are taken verbatim from - the Apple IIe Technical Reference. Addresses are the conventional - locations within the Apple II memory map. Only those which affect - video output are implemented here. + /* + Descriptions for the setters below are taken verbatim from + the Apple IIe Technical Reference. Addresses are the conventional + locations within the Apple II memory map. Only those which affect + video output are implemented here. - Those registers which don't exist on a II/II+ are marked. - */ + Those registers which don't exist on a II/II+ are marked. + */ - /*! - Setter for ALTCHAR ($C00E/$C00F; triggers on write only): + /*! + Setter for ALTCHAR ($C00E/$C00F; triggers on write only): - * Off: display text using primary character set. - * On: display text using alternate character set. + * Off: display text using primary character set. + * On: display text using alternate character set. - Doesn't exist on a II/II+. - */ - void set_alternative_character_set(bool alternative_character_set) { - external_.alternative_character_set = alternative_character_set; - deferrer_.defer(delay_, [this, alternative_character_set] { - internal_.alternative_character_set = alternative_character_set; + Doesn't exist on a II/II+. + */ + void set_alternative_character_set(bool alternative_character_set) { + external_.alternative_character_set = alternative_character_set; + deferrer_.defer(delay_, [this, alternative_character_set] { + internal_.alternative_character_set = alternative_character_set; - if(alternative_character_set) { - character_zones_[1].address_mask = 0xff; - character_zones_[1].xor_mask = 0xff; - } else { - character_zones_[1].address_mask = 0x3f; - character_zones_[1].xor_mask = flash_mask(); - // The XOR mask is seeded here; it's dynamic, so updated elsewhere. - } - }); - } - bool get_alternative_character_set() const { - return external_.alternative_character_set; - } - - /*! - Setter for 80COL ($C00C/$C00D; triggers on write only). - - * Off: display 40 columns. - * On: display 80 columns. - - Doesn't exist on a II/II+. - */ - void set_80_columns(bool columns_80) { - external_.columns_80 = columns_80; - deferrer_.defer(delay_, [this, columns_80] { - internal_.columns_80 = columns_80; - }); - } - bool get_80_columns() const { - return external_.columns_80; - } - - /*! - Setter for 80STORE ($C000/$C001; triggers on write only). - - * Off: cause PAGE2 to select auxiliary RAM. - * On: cause PAGE2 to switch main RAM areas. - - Doesn't exist on a II/II+. - */ - void set_80_store(bool store_80) { - external_.store_80 = internal_.store_80 = store_80; - } - bool get_80_store() const { - return external_.store_80; - } - - /*! - Setter for PAGE2 ($C054/$C055; triggers on read or write). - - * Off: select Page 1. - * On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. - - 80STORE doesn't exist on a II/II+; therefore this always selects - either Page 1 or Page 2 on those machines. - */ - void set_page2(bool page2) { - external_.page2 = internal_.page2 = page2; - } - bool get_page2() const { - return external_.page2; - } - - /*! - Setter for TEXT ($C050/$C051; triggers on read or write). - - * Off: display graphics or, if MIXED on, mixed. - * On: display text. - */ - void set_text(bool text) { - external_.text = text; - deferrer_.defer(delay_, [this, text] { - internal_.text = text; - }); - } - bool get_text() const { - return external_.text; - } - - /*! - Setter for MIXED ($C052/$C053; triggers on read or write). - - * Off: display only text or only graphics. - * On: if TEXT off, display text and graphics. - */ - void set_mixed(bool mixed) { - external_.mixed = mixed; - deferrer_.defer(delay_, [this, mixed] { - internal_.mixed = mixed; - }); - } - bool get_mixed() const { - return external_.mixed; - } - - /*! - Setter for HIRES ($C056/$C057; triggers on read or write). - - * Off: if TEXT off, display low-resolution graphics. - * On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. - - DHIRES doesn't exist on a II/II+; therefore this always selects - either high- or low-resolution graphics on those machines. - - Despite Apple's documentation, the IIe also supports double low-resolution - graphics, which are the 80-column analogue to ordinary low-resolution 40-column - low-resolution graphics. - */ - void set_high_resolution(bool high_resolution) { - external_.high_resolution = high_resolution; - deferrer_.defer(delay_, [this, high_resolution] { - internal_.high_resolution = high_resolution; - }); - } - bool get_high_resolution() const { - return external_.high_resolution; - } - - /*! - Setter for annunciator 3. - - * On: turn on annunciator 3. - * Off: turn off annunciator 3. - - This exists on both the II/II+ and the IIe, but has no effect on - video on the older machines. It's intended to be used on the IIe - to confirm double-high resolution mode but has side effects in - selecting mixed mode output and discarding high-resolution - delay bits. - */ - void set_annunciator_3(bool annunciator_3) { - external_.annunciator_3 = annunciator_3; - deferrer_.defer(delay_, [this, annunciator_3] { - internal_.annunciator_3 = annunciator_3; - high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff; - }); - } - bool get_annunciator_3() const { - return external_.annunciator_3; - } - - /// Set the character ROM for this video output. - void set_character_rom(const std::vector &rom) { - character_rom_ = rom; - - // There's some inconsistency in bit ordering amongst the common ROM dumps; - // detect that based arbitrarily on the second line of the $ graphic and - // ensure consistency. - if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) { - for(auto &graphic : character_rom_) { - graphic = - ((graphic & 0x01) ? 0x40 : 0x00) | - ((graphic & 0x02) ? 0x20 : 0x00) | - ((graphic & 0x04) ? 0x10 : 0x00) | - ((graphic & 0x08) ? 0x08 : 0x00) | - ((graphic & 0x10) ? 0x04 : 0x00) | - ((graphic & 0x20) ? 0x02 : 0x00) | - ((graphic & 0x40) ? 0x01 : 0x00); - } - } - } - - bool has_deferred_actions() const { - return !deferrer_.empty(); - } - - protected: - GraphicsMode graphics_mode(int row) const { - if( - internal_.text || - (internal_.mixed && row >= 160 && row < 192) - ) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text; - if(internal_.high_resolution) { - return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; + if(alternative_character_set) { + character_zones_[1].address_mask = 0xff; + character_zones_[1].xor_mask = 0xff; } else { - if(internal_.columns_80) return GraphicsMode::DoubleLowRes; - if(internal_.annunciator_3) return GraphicsMode::FatLowRes; - return GraphicsMode::LowRes; - } - } - - int video_page() const { - return (internal_.store_80 || !internal_.page2) ? 0 : 1; - } - - uint16_t get_row_address(int row) const { - const int character_row = row >> 3; - const int pixel_row = row & 7; - const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7)); - - const GraphicsMode pixel_mode = graphics_mode(row); - return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? - uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : - uint16_t(((video_page()+1) * 0x400) + row_address); - } - - /*! - Should be called by subclasses at the end of each line of the display; - this gives the base class a peg on which to hang flashing-character updates. - */ - void did_end_line() { - // Update character set flashing; flashing is applied only when the alternative - // character set is not selected. - flash_ = (flash_ + 1) % (2 * flash_length); - if(!internal_.alternative_character_set) { + character_zones_[1].address_mask = 0x3f; character_zones_[1].xor_mask = flash_mask(); + // The XOR mask is seeded here; it's dynamic, so updated elsewhere. + } + }); + } + bool get_alternative_character_set() const { + return external_.alternative_character_set; + } + + /*! + Setter for 80COL ($C00C/$C00D; triggers on write only). + + * Off: display 40 columns. + * On: display 80 columns. + + Doesn't exist on a II/II+. + */ + void set_80_columns(bool columns_80) { + external_.columns_80 = columns_80; + deferrer_.defer(delay_, [this, columns_80] { + internal_.columns_80 = columns_80; + }); + } + bool get_80_columns() const { + return external_.columns_80; + } + + /*! + Setter for 80STORE ($C000/$C001; triggers on write only). + + * Off: cause PAGE2 to select auxiliary RAM. + * On: cause PAGE2 to switch main RAM areas. + + Doesn't exist on a II/II+. + */ + void set_80_store(bool store_80) { + external_.store_80 = internal_.store_80 = store_80; + } + bool get_80_store() const { + return external_.store_80; + } + + /*! + Setter for PAGE2 ($C054/$C055; triggers on read or write). + + * Off: select Page 1. + * On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. + + 80STORE doesn't exist on a II/II+; therefore this always selects + either Page 1 or Page 2 on those machines. + */ + void set_page2(bool page2) { + external_.page2 = internal_.page2 = page2; + } + bool get_page2() const { + return external_.page2; + } + + /*! + Setter for TEXT ($C050/$C051; triggers on read or write). + + * Off: display graphics or, if MIXED on, mixed. + * On: display text. + */ + void set_text(bool text) { + external_.text = text; + deferrer_.defer(delay_, [this, text] { + internal_.text = text; + }); + } + bool get_text() const { + return external_.text; + } + + /*! + Setter for MIXED ($C052/$C053; triggers on read or write). + + * Off: display only text or only graphics. + * On: if TEXT off, display text and graphics. + */ + void set_mixed(bool mixed) { + external_.mixed = mixed; + deferrer_.defer(delay_, [this, mixed] { + internal_.mixed = mixed; + }); + } + bool get_mixed() const { + return external_.mixed; + } + + /*! + Setter for HIRES ($C056/$C057; triggers on read or write). + + * Off: if TEXT off, display low-resolution graphics. + * On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. + + DHIRES doesn't exist on a II/II+; therefore this always selects + either high- or low-resolution graphics on those machines. + + Despite Apple's documentation, the IIe also supports double low-resolution + graphics, which are the 80-column analogue to ordinary low-resolution 40-column + low-resolution graphics. + */ + void set_high_resolution(bool high_resolution) { + external_.high_resolution = high_resolution; + deferrer_.defer(delay_, [this, high_resolution] { + internal_.high_resolution = high_resolution; + }); + } + bool get_high_resolution() const { + return external_.high_resolution; + } + + /*! + Setter for annunciator 3. + + * On: turn on annunciator 3. + * Off: turn off annunciator 3. + + This exists on both the II/II+ and the IIe, but has no effect on + video on the older machines. It's intended to be used on the IIe + to confirm double-high resolution mode but has side effects in + selecting mixed mode output and discarding high-resolution + delay bits. + */ + void set_annunciator_3(bool annunciator_3) { + external_.annunciator_3 = annunciator_3; + deferrer_.defer(delay_, [this, annunciator_3] { + internal_.annunciator_3 = annunciator_3; + high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff; + }); + } + bool get_annunciator_3() const { + return external_.annunciator_3; + } + + /// Set the character ROM for this video output. + void set_character_rom(const std::vector &rom) { + character_rom_ = rom; + + // There's some inconsistency in bit ordering amongst the common ROM dumps; + // detect that based arbitrarily on the second line of the $ graphic and + // ensure consistency. + if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) { + for(auto &graphic : character_rom_) { + graphic = + ((graphic & 0x01) ? 0x40 : 0x00) | + ((graphic & 0x02) ? 0x20 : 0x00) | + ((graphic & 0x04) ? 0x10 : 0x00) | + ((graphic & 0x08) ? 0x08 : 0x00) | + ((graphic & 0x10) ? 0x04 : 0x00) | + ((graphic & 0x20) ? 0x02 : 0x00) | + ((graphic & 0x40) ? 0x01 : 0x00); } } + } - private: - // Maintain a DeferredQueue for delayed mode switches. - const TimeUnit delay_; - DeferredQueuePerformer deferrer_; + bool has_deferred_actions() const { + return !deferrer_.empty(); + } - struct Switches { - bool alternative_character_set = false; - bool columns_80 = false; - bool store_80 = false; - bool page2 = false; - bool text = true; - bool mixed = false; - bool high_resolution = false; - bool annunciator_3 = false; - } external_, internal_; - - // 8406 lines covers a complete on-off cycle, i.e. because the Apple II has a line - // rate of around 15734.26 lines/second, flashing has a frequency of roughly - // 15734.26 / 8406, or roughly 1.83Hz. - // - // Internally that's modelled by a counter that goes to **twice** the value of the - // constant specified below. Hence the divide by two. - static constexpr int flash_length = 8406 / 2; - - int flash_ = 0; - uint8_t flash_mask() const { - return uint8_t((flash_ / flash_length) * 0xff); +protected: + GraphicsMode graphics_mode(int row) const { + if( + internal_.text || + (internal_.mixed && row >= 160 && row < 192) + ) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text; + if(internal_.high_resolution) { + return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; + } else { + if(internal_.columns_80) return GraphicsMode::DoubleLowRes; + if(internal_.annunciator_3) return GraphicsMode::FatLowRes; + return GraphicsMode::LowRes; } + } - protected: + int video_page() const { + return (internal_.store_80 || !internal_.page2) ? 0 : 1; + } - // Describes the current text mode mapping from in-memory character index - // to output character; subclasses should: - // - // (i) use the top two-bits of the character code to index character_zones_; - // (ii) apply the address_mask to the character code in order to get a character - // offset into the character ROM; and - // (iii) apply the XOR mask to the output of the character ROM. - // - // By this means they will properly handle the limited character sets of Apple IIs - // prior to the IIe as well as the IIe and onward's alternative character set toggle. - struct CharacterMapping { - uint8_t address_mask; - uint8_t xor_mask; - }; - CharacterMapping character_zones_[4]; + uint16_t get_row_address(int row) const { + const int character_row = row >> 3; + const int pixel_row = row & 7; + const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7)); - // A mask that should be applied to high-resolution graphics bytes before output; - // it acts to retain or remove the top bit, affecting whether the half-pixel delay - // bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist. - uint8_t high_resolution_mask_ = 0xff; + const GraphicsMode pixel_mode = graphics_mode(row); + return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? + uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : + uint16_t(((video_page()+1) * 0x400) + row_address); + } - // This holds a copy of the character ROM. The regular character - // set is assumed to be in the first 64*8 bytes; the alternative - // is in the 128*8 bytes after that. - std::vector character_rom_; + /*! + Should be called by subclasses at the end of each line of the display; + this gives the base class a peg on which to hang flashing-character updates. + */ + void did_end_line() { + // Update character set flashing; flashing is applied only when the alternative + // character set is not selected. + flash_ = (flash_ + 1) % (2 * flash_length); + if(!internal_.alternative_character_set) { + character_zones_[1].xor_mask = flash_mask(); + } + } + +private: + // Maintain a DeferredQueue for delayed mode switches. + const TimeUnit delay_; + DeferredQueuePerformer deferrer_; + + struct Switches { + bool alternative_character_set = false; + bool columns_80 = false; + bool store_80 = false; + bool page2 = false; + bool text = true; + bool mixed = false; + bool high_resolution = false; + bool annunciator_3 = false; + } external_, internal_; + + // 8406 lines covers a complete on-off cycle, i.e. because the Apple II has a line + // rate of around 15734.26 lines/second, flashing has a frequency of roughly + // 15734.26 / 8406, or roughly 1.83Hz. + // + // Internally that's modelled by a counter that goes to **twice** the value of the + // constant specified below. Hence the divide by two. + static constexpr int flash_length = 8406 / 2; + + int flash_ = 0; + uint8_t flash_mask() const { + return uint8_t((flash_ / flash_length) * 0xff); + } + +protected: + + // Describes the current text mode mapping from in-memory character index + // to output character; subclasses should: + // + // (i) use the top two-bits of the character code to index character_zones_; + // (ii) apply the address_mask to the character code in order to get a character + // offset into the character ROM; and + // (iii) apply the XOR mask to the output of the character ROM. + // + // By this means they will properly handle the limited character sets of Apple IIs + // prior to the IIe as well as the IIe and onward's alternative character set toggle. + struct CharacterMapping { + uint8_t address_mask; + uint8_t xor_mask; + }; + CharacterMapping character_zones_[4]; + + // A mask that should be applied to high-resolution graphics bytes before output; + // it acts to retain or remove the top bit, affecting whether the half-pixel delay + // bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist. + uint8_t high_resolution_mask_ = 0xff; + + // This holds a copy of the character ROM. The regular character + // set is assumed to be in the first 64*8 bytes; the alternative + // is in the 128*8 bytes after that. + std::vector character_rom_; }; } diff --git a/Machines/Atari/2600/Atari2600.cpp b/Machines/Atari/2600/Atari2600.cpp index 030a835e4..36db0226a 100644 --- a/Machines/Atari/2600/Atari2600.cpp +++ b/Machines/Atari/2600/Atari2600.cpp @@ -138,7 +138,7 @@ class ConcreteMachine: } } - bool get_switch_is_enabled(Atari2600Switch input) final { + bool get_switch_is_enabled(Atari2600Switch input) const final { uint8_t port_input = bus_->mos6532_.get_port_input(1); switch(input) { case Atari2600SwitchReset: return !!(port_input & 0x01); diff --git a/Machines/Atari/2600/Atari2600.hpp b/Machines/Atari/2600/Atari2600.hpp index a4391aaf4..24d72b7fa 100644 --- a/Machines/Atari/2600/Atari2600.hpp +++ b/Machines/Atari/2600/Atari2600.hpp @@ -22,20 +22,20 @@ namespace Atari2600 { Models an Atari 2600. */ class Machine { - public: - virtual ~Machine() = default; +public: + virtual ~Machine() = default; - /// Creates and returns an Atari 2600 on the heap. - static std::unique_ptr Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + /// Creates and returns an Atari 2600 on the heap. + static std::unique_ptr Atari2600(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &); - /// Sets the switch @c input to @c state. - virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0; + /// Sets the switch @c input to @c state. + virtual void set_switch_is_enabled(Atari2600Switch, bool) = 0; - /// Gets the state of switch @c input. - virtual bool get_switch_is_enabled(Atari2600Switch input) = 0; + /// Gets the state of switch @c input. + virtual bool get_switch_is_enabled(Atari2600Switch) const = 0; - // Presses or releases the reset button. - virtual void set_reset_switch(bool state) = 0; + // Presses or releases the reset button. + virtual void set_reset_switch(bool) = 0; }; } diff --git a/Machines/Atari/2600/Bus.hpp b/Machines/Atari/2600/Bus.hpp index 80e3f8354..8ca72f41b 100644 --- a/Machines/Atari/2600/Bus.hpp +++ b/Machines/Atari/2600/Bus.hpp @@ -20,49 +20,49 @@ namespace Atari2600 { class Bus { - public: - Bus() : - tia_sound_(audio_queue_), - speaker_(tia_sound_) {} +public: + Bus() : + tia_sound_(audio_queue_), + speaker_(tia_sound_) {} - virtual ~Bus() { - audio_queue_.flush(); - } + virtual ~Bus() { + audio_queue_.flush(); + } - virtual void run_for(const Cycles cycles) = 0; - virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0; - virtual void set_reset_line(bool state) = 0; - virtual void flush() = 0; + virtual void run_for(const Cycles cycles) = 0; + virtual void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) = 0; + virtual void set_reset_line(bool state) = 0; + virtual void flush() = 0; - // the RIOT, TIA and speaker - PIA mos6532_; - TIA tia_; + // the RIOT, TIA and speaker + PIA mos6532_; + TIA tia_; - Concurrency::AsyncTaskQueue audio_queue_; - TIASound tia_sound_; - Outputs::Speaker::PullLowpass speaker_; + Concurrency::AsyncTaskQueue audio_queue_; + TIASound tia_sound_; + Outputs::Speaker::PullLowpass speaker_; - // joystick state - uint8_t tia_input_value_[2] = {0xff, 0xff}; + // joystick state + uint8_t tia_input_value_[2] = {0xff, 0xff}; - protected: - // speaker backlog accumlation counter - Cycles cycles_since_speaker_update_; - inline void update_audio() { - speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); - } +protected: + // speaker backlog accumlation counter + Cycles cycles_since_speaker_update_; + inline void update_audio() { + speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3))); + } - // video backlog accumulation counter - Cycles cycles_since_video_update_; - inline void update_video() { - tia_.run_for(cycles_since_video_update_.flush()); - } + // video backlog accumulation counter + Cycles cycles_since_video_update_; + inline void update_video() { + tia_.run_for(cycles_since_video_update_.flush()); + } - // RIOT backlog accumulation counter - Cycles cycles_since_6532_update_; - inline void update_6532() { - mos6532_.run_for(cycles_since_6532_update_.flush()); - } + // RIOT backlog accumulation counter + Cycles cycles_since_6532_update_; + inline void update_6532() { + mos6532_.run_for(cycles_since_6532_update_.flush()); + } }; } diff --git a/Machines/Atari/2600/Cartridges/ActivisionStack.hpp b/Machines/Atari/2600/Cartridges/ActivisionStack.hpp index 6eca92fb8..4d067992f 100644 --- a/Machines/Atari/2600/Cartridges/ActivisionStack.hpp +++ b/Machines/Atari/2600/Cartridges/ActivisionStack.hpp @@ -11,36 +11,40 @@ namespace Atari2600::Cartridge { class ActivisionStack: public BusExtender { - public: - ActivisionStack(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size), - rom_ptr_(rom_base), - last_opcode_(0x00) {} +public: + ActivisionStack(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), + rom_ptr_(rom_base), + last_opcode_(0x00) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + const uint16_t address, + uint8_t *const value + ) { + if(!(address & 0x1000)) return; - // This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see - // address line 13. Instead it looks for a pattern in recent address accesses that would imply an - // RST or JSR. - if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { - if(address & 0x2000) { - rom_ptr_ = rom_base_; - } else { - rom_ptr_ = rom_base_ + 4096; - } + // This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see + // address line 13. Instead it looks for a pattern in recent address accesses that would imply an + // RST or JSR. + if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) { + if(address & 0x2000) { + rom_ptr_ = rom_base_; + } else { + rom_ptr_ = rom_base_ + 4096; } - - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } - - if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value; } - private: - uint8_t *rom_ptr_; - uint8_t last_opcode_; + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; + } + + if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value; + } + +private: + const uint8_t *rom_ptr_; + uint8_t last_opcode_; }; } diff --git a/Machines/Atari/2600/Cartridges/Atari16k.hpp b/Machines/Atari/2600/Cartridges/Atari16k.hpp index 325e79efd..58b035370 100644 --- a/Machines/Atari/2600/Cartridges/Atari16k.hpp +++ b/Machines/Atari/2600/Cartridges/Atari16k.hpp @@ -13,49 +13,57 @@ namespace Atari2600::Cartridge { class Atari16k: public BusExtender { - public: - Atari16k(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size), - rom_ptr_(rom_base) {} +public: + Atari16k(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), + rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; + if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } + } - private: - uint8_t *rom_ptr_; +private: + const uint8_t *rom_ptr_; }; class Atari16kSuperChip: public BusExtender { - public: - Atari16kSuperChip(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size), - rom_ptr_(rom_base) {} +public: + Atari16kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), + rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; + if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } - - if(address < 0x1080) ram_[address & 0x7f] = *value; - else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } - private: - uint8_t *rom_ptr_; - uint8_t ram_[128]; + if(address < 0x1080) ram_[address & 0x7f] = *value; + else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; + } + +private: + const uint8_t *rom_ptr_; + uint8_t ram_[128]; }; } diff --git a/Machines/Atari/2600/Cartridges/Atari32k.hpp b/Machines/Atari/2600/Cartridges/Atari32k.hpp index e130f1a2c..890b99cc6 100644 --- a/Machines/Atari/2600/Cartridges/Atari32k.hpp +++ b/Machines/Atari/2600/Cartridges/Atari32k.hpp @@ -13,45 +13,55 @@ namespace Atari2600::Cartridge { class Atari32k: public BusExtender { - public: - Atari32k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} +public: + Atari32k(const uint8_t *const rom_base, const std::size_t rom_size) + : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; + if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } + } - private: - uint8_t *rom_ptr_; +private: + const uint8_t *rom_ptr_; }; class Atari32kSuperChip: public BusExtender { - public: - Atari32kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} +public: + Atari32kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) + : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; + if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } - - if(address < 0x1080) ram_[address & 0x7f] = *value; - else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } - private: - uint8_t *rom_ptr_; - uint8_t ram_[128]; + if(address < 0x1080) ram_[address & 0x7f] = *value; + else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; + } + +private: + const uint8_t *rom_ptr_; + uint8_t ram_[128]; }; } diff --git a/Machines/Atari/2600/Cartridges/Atari8k.hpp b/Machines/Atari/2600/Cartridges/Atari8k.hpp index 03987d022..72fa699bf 100644 --- a/Machines/Atari/2600/Cartridges/Atari8k.hpp +++ b/Machines/Atari/2600/Cartridges/Atari8k.hpp @@ -13,47 +13,57 @@ namespace Atari2600::Cartridge { class Atari8k: public BusExtender { - public: - Atari8k(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} +public: + Atari8k(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address == 0x1ff8) rom_ptr_ = rom_base_; - else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; + if(address == 0x1ff8) rom_ptr_ = rom_base_; + else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } + } - private: - uint8_t *rom_ptr_; +private: + const uint8_t *rom_ptr_; }; class Atari8kSuperChip: public BusExtender { - public: - Atari8kSuperChip(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} +public: + Atari8kSuperChip(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address == 0x1ff8) rom_ptr_ = rom_base_; - if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; + if(address == 0x1ff8) rom_ptr_ = rom_base_; + if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } - - if(address < 0x1080) ram_[address & 0x7f] = *value; - else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } - private: - uint8_t *rom_ptr_; - uint8_t ram_[128]; + if(address < 0x1080) ram_[address & 0x7f] = *value; + else if(address < 0x1100 && isReadOperation(operation)) *value = ram_[address & 0x7f]; + } + +private: + const uint8_t *rom_ptr_; + uint8_t ram_[128]; }; } diff --git a/Machines/Atari/2600/Cartridges/CBSRAMPlus.hpp b/Machines/Atari/2600/Cartridges/CBSRAMPlus.hpp index 3fd9586b4..6e38f6c06 100644 --- a/Machines/Atari/2600/Cartridges/CBSRAMPlus.hpp +++ b/Machines/Atari/2600/Cartridges/CBSRAMPlus.hpp @@ -13,26 +13,31 @@ namespace Atari2600::Cartridge { class CBSRAMPlus: public BusExtender { - public: - CBSRAMPlus(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} +public: + CBSRAMPlus(const uint8_t *const rom_base, std::size_t rom_size) : + BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; - if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096; + if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096; - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } - - if(address < 0x1100) ram_[address & 0xff] = *value; - else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff]; + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } - private: - uint8_t *rom_ptr_; - uint8_t ram_[256]; + if(address < 0x1100) ram_[address & 0xff] = *value; + else if(address < 0x1200 && isReadOperation(operation)) *value = ram_[address & 0xff]; + } + +private: + const uint8_t *rom_ptr_; + uint8_t ram_[256]; }; } diff --git a/Machines/Atari/2600/Cartridges/Cartridge.hpp b/Machines/Atari/2600/Cartridges/Cartridge.hpp index bec59ffaf..8cbf48388 100644 --- a/Machines/Atari/2600/Cartridges/Cartridge.hpp +++ b/Machines/Atari/2600/Cartridges/Cartridge.hpp @@ -14,202 +14,206 @@ namespace Atari2600::Cartridge { class BusExtender: public CPU::MOS6502::BusHandler { - public: - BusExtender(uint8_t *rom_base, std::size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {} +public: + BusExtender(const uint8_t *const rom_base, const std::size_t rom_size) : + rom_base_(rom_base), rom_size_(rom_size) {} - void advance_cycles(int) {} + void advance_cycles(int) {} - protected: - uint8_t *rom_base_; - std::size_t rom_size_; +protected: + const uint8_t *rom_base_; + std::size_t rom_size_; }; template class Cartridge: public CPU::MOS6502::BusHandler, public Bus { - public: - Cartridge(const std::vector &rom) : - m6502_(*this), - rom_(rom), - bus_extender_(rom_.data(), rom.size()) { - // The above works because bus_extender_ is declared after rom_ in the instance storage list; - // consider doing something less fragile. - } +public: + Cartridge(const std::vector &rom) : + m6502_(*this), + rom_(rom), + bus_extender_(rom_.data(), rom.size()) { + // The above works because bus_extender_ is declared after rom_ in the instance storage list; + // consider doing something less fragile. + } - void run_for(const Cycles cycles) override { - // Horizontal counter resets are used as a proxy for whether this really is an Atari 2600 - // title. Random memory accesses are likely to trigger random counter resets. - horizontal_counter_resets_ = 0; - cycle_count_ = cycles; - m6502_.run_for(cycles); - } + void run_for(const Cycles cycles) override { + // Horizontal counter resets are used as a proxy for whether this really is an Atari 2600 + // title. Random memory accesses are likely to trigger random counter resets. + horizontal_counter_resets_ = 0; + cycle_count_ = cycles; + m6502_.run_for(cycles); + } - /*! - Adjusts @c confidence_counter according to the results of the most recent run_for. - */ - void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override { - if(cycle_count_.as_integral() < 200) return; - if(horizontal_counter_resets_ > 10) - confidence_counter.add_miss(); - } + /*! + Adjusts @c confidence_counter according to the results of the most recent run_for. + */ + void apply_confidence(Analyser::Dynamic::ConfidenceCounter &confidence_counter) override { + if(cycle_count_.as_integral() < 200) return; + if(horizontal_counter_resets_ > 10) + confidence_counter.add_miss(); + } - void set_reset_line(bool state) override { m6502_.set_reset_line(state); } + void set_reset_line(const bool state) override { m6502_.set_reset_line(state); } - // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - uint8_t returnValue = 0xff; - int cycles_run_for = 3; + // to satisfy CPU::MOS6502::Processor + Cycles perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + const uint16_t address, + uint8_t *const value + ) { + uint8_t returnValue = 0xff; + int cycles_run_for = 3; - // this occurs as a feedback loop: the 2600 requests ready, then performs the cycles_run_for - // leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take - // effect until the next read; therefore it isn't safe to assume that signalling ready immediately - // skips to the end of the line. - if(operation == CPU::MOS6502::BusOperation::Ready) - cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_); + // this occurs as a feedback loop: the 2600 requests ready, then performs the cycles_run_for + // leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take + // effect until the next read; therefore it isn't safe to assume that signalling ready immediately + // skips to the end of the line. + if(operation == CPU::MOS6502::BusOperation::Ready) + cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_); - cycles_since_speaker_update_ += Cycles(cycles_run_for); - cycles_since_video_update_ += Cycles(cycles_run_for); - cycles_since_6532_update_ += Cycles(cycles_run_for / 3); - bus_extender_.advance_cycles(cycles_run_for / 3); + cycles_since_speaker_update_ += Cycles(cycles_run_for); + cycles_since_video_update_ += Cycles(cycles_run_for); + cycles_since_6532_update_ += Cycles(cycles_run_for / 3); + bus_extender_.advance_cycles(cycles_run_for / 3); - if(isAccessOperation(operation)) { - // give the cartridge a chance to respond to the bus access - bus_extender_.perform_bus_operation(operation, address, value); - - // check for a RIOT RAM access - if((address&0x1280) == 0x80) { - if(isReadOperation(operation)) { - returnValue &= mos6532_.get_ram(address); - } else { - mos6532_.set_ram(address, *value); - } - } - - // check for a TIA access - if(!(address&0x1080)) { - if(isReadOperation(operation)) { - const uint16_t decodedAddress = address & 0xf; - switch(decodedAddress) { - case 0x00: // missile 0 / player collisions - case 0x01: // missile 1 / player collisions - case 0x02: // player 0 / playfield / ball collisions - case 0x03: // player 1 / playfield / ball collisions - case 0x04: // missile 0 / playfield / ball collisions - case 0x05: // missile 1 / playfield / ball collisions - case 0x06: // ball / playfield collisions - case 0x07: // player / player, missile / missile collisions - returnValue &= tia_.get_collision_flags(decodedAddress); - break; - - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - // TODO: pot ports - returnValue &= 0; - break; - - case 0x0c: - case 0x0d: - returnValue &= tia_input_value_[decodedAddress - 0x0c]; - break; - } - } else { - const uint16_t decodedAddress = address & 0x3f; - switch(decodedAddress) { - case 0x00: update_video(); tia_.set_sync(*value & 0x02); break; - case 0x01: update_video(); tia_.set_blank(*value & 0x02); break; - - case 0x02: m6502_.set_ready_line(true); break; - case 0x03: - update_video(); - tia_.reset_horizontal_counter(); - horizontal_counter_resets_++; - break; - // TODO: audio will now be out of synchronisation. Fix. - - case 0x04: - case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break; - case 0x06: - case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break; - case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break; - case 0x09: update_video(); tia_.set_background_colour(*value); break; - case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break; - case 0x0b: - case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break; - case 0x0d: - case 0x0e: - case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break; - case 0x10: - case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break; - case 0x12: - case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break; - case 0x14: update_video(); tia_.set_ball_position(); break; - case 0x1b: - case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break; - case 0x1d: - case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break; - case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break; - case 0x20: - case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break; - case 0x22: - case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break; - case 0x24: update_video(); tia_.set_ball_motion(*value); break; - case 0x25: - case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break; - case 0x27: tia_.set_ball_delay((*value)&1); break; - case 0x28: - case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break; - case 0x2a: update_video(); tia_.move(); break; - case 0x2b: update_video(); tia_.clear_motion(); break; - case 0x2c: update_video(); tia_.clear_collision_flags(); break; - - case 0x15: - case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break; - case 0x17: - case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break; - case 0x19: - case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break; - } - } - } - - // check for a PIA access - if((address&0x1280) == 0x280) { - update_6532(); - if(isReadOperation(operation)) { - returnValue &= mos6532_.read(address); - } else { - mos6532_.write(address, *value); - } - } + if(isAccessOperation(operation)) { + // give the cartridge a chance to respond to the bus access + bus_extender_.perform_bus_operation(operation, address, value); + // check for a RIOT RAM access + if((address&0x1280) == 0x80) { if(isReadOperation(operation)) { - *value &= returnValue; + returnValue &= mos6532_.get_ram(address); + } else { + mos6532_.set_ram(address, *value); } } - if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false); + // check for a TIA access + if(!(address&0x1080)) { + if(isReadOperation(operation)) { + const uint16_t decodedAddress = address & 0xf; + switch(decodedAddress) { + case 0x00: // missile 0 / player collisions + case 0x01: // missile 1 / player collisions + case 0x02: // player 0 / playfield / ball collisions + case 0x03: // player 1 / playfield / ball collisions + case 0x04: // missile 0 / playfield / ball collisions + case 0x05: // missile 1 / playfield / ball collisions + case 0x06: // ball / playfield collisions + case 0x07: // player / player, missile / missile collisions + returnValue &= tia_.get_collision_flags(decodedAddress); + break; - return Cycles(cycles_run_for / 3); + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + // TODO: pot ports + returnValue &= 0; + break; + + case 0x0c: + case 0x0d: + returnValue &= tia_input_value_[decodedAddress - 0x0c]; + break; + } + } else { + const uint16_t decodedAddress = address & 0x3f; + switch(decodedAddress) { + case 0x00: update_video(); tia_.set_sync(*value & 0x02); break; + case 0x01: update_video(); tia_.set_blank(*value & 0x02); break; + + case 0x02: m6502_.set_ready_line(true); break; + case 0x03: + update_video(); + tia_.reset_horizontal_counter(); + horizontal_counter_resets_++; + break; + // TODO: audio will now be out of synchronisation. Fix. + + case 0x04: + case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break; + case 0x06: + case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break; + case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break; + case 0x09: update_video(); tia_.set_background_colour(*value); break; + case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break; + case 0x0b: + case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break; + case 0x0d: + case 0x0e: + case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break; + case 0x10: + case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break; + case 0x12: + case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break; + case 0x14: update_video(); tia_.set_ball_position(); break; + case 0x1b: + case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break; + case 0x1d: + case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break; + case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break; + case 0x20: + case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break; + case 0x22: + case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break; + case 0x24: update_video(); tia_.set_ball_motion(*value); break; + case 0x25: + case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break; + case 0x27: tia_.set_ball_delay((*value)&1); break; + case 0x28: + case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break; + case 0x2a: update_video(); tia_.move(); break; + case 0x2b: update_video(); tia_.clear_motion(); break; + case 0x2c: update_video(); tia_.clear_collision_flags(); break; + + case 0x15: + case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break; + case 0x17: + case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break; + case 0x19: + case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break; + } + } + } + + // check for a PIA access + if((address&0x1280) == 0x280) { + update_6532(); + if(isReadOperation(operation)) { + returnValue &= mos6532_.read(address); + } else { + mos6532_.write(address, *value); + } + } + + if(isReadOperation(operation)) { + *value &= returnValue; + } } - void flush() override { - update_audio(); - update_video(); - audio_queue_.perform(); - } + if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false); - protected: - CPU::MOS6502::Processor, true> m6502_; - std::vector rom_; + return Cycles(cycles_run_for / 3); + } - private: - T bus_extender_; - int horizontal_counter_resets_ = 0; - Cycles cycle_count_; + void flush() override { + update_audio(); + update_video(); + audio_queue_.perform(); + } +protected: + CPU::MOS6502::Processor, true> m6502_; + std::vector rom_; + +private: + T bus_extender_; + int horizontal_counter_resets_ = 0; + Cycles cycle_count_; }; } diff --git a/Machines/Atari/2600/Cartridges/CommaVid.hpp b/Machines/Atari/2600/Cartridges/CommaVid.hpp index 0be7f5919..1627df248 100644 --- a/Machines/Atari/2600/Cartridges/CommaVid.hpp +++ b/Machines/Atari/2600/Cartridges/CommaVid.hpp @@ -13,28 +13,32 @@ namespace Atari2600::Cartridge { class CommaVid: public BusExtender { - public: - CommaVid(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {} +public: + CommaVid(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - if(!(address & 0x1000)) return; - address &= 0x1fff; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + if(!(address & 0x1000)) return; + address &= 0x1fff; - if(address < 0x1400) { - if(isReadOperation(operation)) *value = ram_[address & 1023]; - return; - } - - if(address < 0x1800) { - ram_[address & 1023] = *value; - return; - } - - if(isReadOperation(operation)) *value = rom_base_[address & 2047]; + if(address < 0x1400) { + if(isReadOperation(operation)) *value = ram_[address & 1023]; + return; } - private: - uint8_t ram_[1024]; + if(address < 0x1800) { + ram_[address & 1023] = *value; + return; + } + + if(isReadOperation(operation)) *value = rom_base_[address & 2047]; + } + +private: + uint8_t ram_[1024]; }; } diff --git a/Machines/Atari/2600/Cartridges/MNetwork.hpp b/Machines/Atari/2600/Cartridges/MNetwork.hpp index 06a6972c0..129a7def8 100644 --- a/Machines/Atari/2600/Cartridges/MNetwork.hpp +++ b/Machines/Atari/2600/Cartridges/MNetwork.hpp @@ -13,52 +13,55 @@ namespace Atari2600::Cartridge { class MNetwork: public BusExtender { - public: - MNetwork(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size) { - rom_ptr_[0] = rom_base + rom_size_ - 4096; - rom_ptr_[1] = rom_ptr_[0] + 2048; - high_ram_ptr_ = high_ram_; +public: + MNetwork(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) { + rom_ptr_[0] = rom_base + rom_size_ - 4096; + rom_ptr_[1] = rom_ptr_[0] + 2048; + high_ram_ptr_ = high_ram_; + } + + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; + + if(address >= 0x1fe0 && address <= 0x1fe6) { + rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048; + } else if(address == 0x1fe7) { + rom_ptr_[0] = nullptr; + } else if(address >= 0x1ff8 && address <= 0x1ffb) { + int offset = (address - 0x1ff8) * 256; + high_ram_ptr_ = &high_ram_[offset]; } - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; - - if(address >= 0x1fe0 && address <= 0x1fe6) { - rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048; - } else if(address == 0x1fe7) { - rom_ptr_[0] = nullptr; - } else if(address >= 0x1ff8 && address <= 0x1ffb) { - int offset = (address - 0x1ff8) * 256; - high_ram_ptr_ = &high_ram_[offset]; - } - - if(address & 0x800) { - if(address < 0x1900) { - high_ram_ptr_[address & 255] = *value; - } else if(address < 0x1a00) { - if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255]; - } else { - if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047]; - } + if(address & 0x800) { + if(address < 0x1900) { + high_ram_ptr_[address & 255] = *value; + } else if(address < 0x1a00) { + if(isReadOperation(operation)) *value = high_ram_ptr_[address & 255]; } else { - if(rom_ptr_[0]) { - if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047]; + if(isReadOperation(operation)) *value = rom_ptr_[1][address & 2047]; + } + } else { + if(rom_ptr_[0]) { + if(isReadOperation(operation)) *value = rom_ptr_[0][address & 2047]; + } else { + if(address < 0x1400) { + low_ram_[address & 1023] = *value; } else { - if(address < 0x1400) { - low_ram_[address & 1023] = *value; - } else { - if(isReadOperation(operation)) *value = low_ram_[address & 1023]; - } + if(isReadOperation(operation)) *value = low_ram_[address & 1023]; } } } + } - private: - uint8_t *rom_ptr_[2]; - uint8_t *high_ram_ptr_; - uint8_t low_ram_[1024], high_ram_[1024]; +private: + const uint8_t *rom_ptr_[2]; + uint8_t *high_ram_ptr_; + uint8_t low_ram_[1024], high_ram_[1024]; }; } diff --git a/Machines/Atari/2600/Cartridges/MegaBoy.hpp b/Machines/Atari/2600/Cartridges/MegaBoy.hpp index fcc021980..8a96c32ea 100644 --- a/Machines/Atari/2600/Cartridges/MegaBoy.hpp +++ b/Machines/Atari/2600/Cartridges/MegaBoy.hpp @@ -13,30 +13,31 @@ namespace Atari2600::Cartridge { class MegaBoy: public BusExtender { - public: - MegaBoy(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size), - rom_ptr_(rom_base), - current_page_(0) { +public: + MegaBoy(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), rom_ptr_(rom_base), current_page_(0) {} + + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; + + if(address == 0x1ff0) { + current_page_ = (current_page_ + 1) & 15; + rom_ptr_ = rom_base_ + current_page_ * 4096; } - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; - - if(address == 0x1ff0) { - current_page_ = (current_page_ + 1) & 15; - rom_ptr_ = rom_base_ + current_page_ * 4096; - } - - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } + } - private: - uint8_t *rom_ptr_; - uint8_t current_page_; +private: + const uint8_t *rom_ptr_; + uint8_t current_page_; }; } diff --git a/Machines/Atari/2600/Cartridges/ParkerBros.hpp b/Machines/Atari/2600/Cartridges/ParkerBros.hpp index 39ce52893..2fda0e756 100644 --- a/Machines/Atari/2600/Cartridges/ParkerBros.hpp +++ b/Machines/Atari/2600/Cartridges/ParkerBros.hpp @@ -13,31 +13,36 @@ namespace Atari2600::Cartridge { class ParkerBros: public BusExtender { - public: - ParkerBros(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size) { - rom_ptr_[0] = rom_base + 4096; - rom_ptr_[1] = rom_ptr_[0] + 1024; - rom_ptr_[2] = rom_ptr_[1] + 1024; - rom_ptr_[3] = rom_ptr_[2] + 1024; +public: + ParkerBros(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size) + { + rom_ptr_[0] = rom_base + 4096; + rom_ptr_[1] = rom_ptr_[0] + 1024; + rom_ptr_[2] = rom_ptr_[1] + 1024; + rom_ptr_[3] = rom_ptr_[2] + 1024; + } + + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value + ) { + address &= 0x1fff; + if(!(address & 0x1000)) return; + + if(address >= 0x1fe0 && address < 0x1ff8) { + int slot = (address >> 3)&3; + rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024); } - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; - - if(address >= 0x1fe0 && address < 0x1ff8) { - int slot = (address >> 3)&3; - rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024); - } - - if(isReadOperation(operation)) { - *value = rom_ptr_[(address >> 10)&3][address & 1023]; - } + if(isReadOperation(operation)) { + *value = rom_ptr_[(address >> 10)&3][address & 1023]; } + } - private: - uint8_t *rom_ptr_[4]; +private: + const uint8_t *rom_ptr_[4]; }; } diff --git a/Machines/Atari/2600/Cartridges/Pitfall2.hpp b/Machines/Atari/2600/Cartridges/Pitfall2.hpp index c28da6433..00e7d2f90 100644 --- a/Machines/Atari/2600/Cartridges/Pitfall2.hpp +++ b/Machines/Atari/2600/Cartridges/Pitfall2.hpp @@ -11,114 +11,118 @@ namespace Atari2600::Cartridge { class Pitfall2: public BusExtender { - public: - Pitfall2(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size), - rom_ptr_(rom_base) {} +public: + Pitfall2(const uint8_t *const rom_base, const std::size_t rom_size) : + BusExtender(rom_base, rom_size), + rom_ptr_(rom_base) {} - void advance_cycles(int cycles) { - cycles_since_audio_update_ += cycles; - } + void advance_cycles(const int cycles) { + cycles_since_audio_update_ += cycles; + } - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - address &= 0x1fff; - if(!(address & 0x1000)) return; + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + uint16_t address, + uint8_t *const value) + { + address &= 0x1fff; + if(!(address & 0x1000)) return; - switch(address) { + switch(address) { // MARK: - Reads - // The random number generator - case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004: - if(isReadOperation(operation)) { - *value = random_number_generator_; - } - random_number_generator_ = uint8_t( - (random_number_generator_ << 1) | - (~( (random_number_generator_ >> 7) ^ - (random_number_generator_ >> 5) ^ - (random_number_generator_ >> 4) ^ - (random_number_generator_ >> 3) - ) & 1)); - break; + // The random number generator + case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004: + if(isReadOperation(operation)) { + *value = random_number_generator_; + } + random_number_generator_ = uint8_t( + (random_number_generator_ << 1) | + (~( (random_number_generator_ >> 7) ^ + (random_number_generator_ >> 5) ^ + (random_number_generator_ >> 4) ^ + (random_number_generator_ >> 3) + ) & 1)); + break; - case 0x1005: case 0x1006: case 0x1007: - *value = update_audio(); - break; + case 0x1005: case 0x1006: case 0x1007: + *value = update_audio(); + break; - case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: - *value = rom_base_[8192 + address_for_counter(address & 7)]; - break; + case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f: + *value = rom_base_[8192 + address_for_counter(address & 7)]; + break; - case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: - *value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; - break; + case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017: + *value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7]; + break; // MARK: - Writes - case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047: - top_[address & 7] = *value; - break; - case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f: - bottom_[address & 7] = *value; - break; - case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057: - featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value; - mask_[address & 7] = 0x00; - break; - case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f: - featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | uint16_t(*value << 8); - break; - case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077: - random_number_generator_ = 0; - break; + case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047: + top_[address & 7] = *value; + break; + case 0x1048: case 0x1049: case 0x104a: case 0x104b: case 0x104c: case 0x104d: case 0x104e: case 0x104f: + bottom_[address & 7] = *value; + break; + case 0x1050: case 0x1051: case 0x1052: case 0x1053: case 0x1054: case 0x1055: case 0x1056: case 0x1057: + featcher_address_[address & 7] = (featcher_address_[address & 7] & 0xff00) | *value; + mask_[address & 7] = 0x00; + break; + case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f: + featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | uint16_t(*value << 8); + break; + case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077: + random_number_generator_ = 0; + break; // MARK: - Paging - case 0x1ff8: rom_ptr_ = rom_base_; break; - case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break; + case 0x1ff8: rom_ptr_ = rom_base_; break; + case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break; // MARK: - Business as usual - default: - if(isReadOperation(operation)) { - *value = rom_ptr_[address & 4095]; - } - break; - } - } - - private: - inline uint16_t address_for_counter(int counter) { - uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047; - if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff; - if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00; - featcher_address_[counter]--; - return fetch_address; - } - - inline uint8_t update_audio() { - const unsigned int clock_divisor = 57; - int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral()); - - int table_position = 0; - for(int c = 0; c < 3; c++) { - audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c])); - if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) { - table_position |= 0x4 >> c; + default: + if(isReadOperation(operation)) { + *value = rom_ptr_[address & 4095]; } - } + break; + } + } - static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf }; - return level_table[table_position]; +private: + inline uint16_t address_for_counter(const int counter) { + uint16_t fetch_address = (featcher_address_[counter] & 2047) ^ 2047; + if((featcher_address_[counter] & 0xff) == top_[counter]) mask_[counter] = 0xff; + if((featcher_address_[counter] & 0xff) == bottom_[counter]) mask_[counter] = 0x00; + featcher_address_[counter]--; + return fetch_address; + } + + inline uint8_t update_audio() { + const unsigned int clock_divisor = 57; + int cycles_to_run_for = int(cycles_since_audio_update_.divide(clock_divisor).as_integral()); + + int table_position = 0; + for(int c = 0; c < 3; c++) { + audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c])); + if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) { + table_position |= 0x4 >> c; + } } - uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - uint8_t random_number_generator_ = 0; - uint8_t *rom_ptr_; - uint8_t audio_channel_[3]; - Cycles cycles_since_audio_update_ = 0; + static uint8_t level_table[8] = { 0x0, 0x4, 0x5, 0x9, 0x6, 0xa, 0xb, 0xf }; + return level_table[table_position]; + } + + uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t random_number_generator_ = 0; + const uint8_t *rom_ptr_; + uint8_t audio_channel_[3]; + Cycles cycles_since_audio_update_ = 0; }; } diff --git a/Machines/Atari/2600/Cartridges/Tigervision.hpp b/Machines/Atari/2600/Cartridges/Tigervision.hpp index ac21071e0..da6d302d9 100644 --- a/Machines/Atari/2600/Cartridges/Tigervision.hpp +++ b/Machines/Atari/2600/Cartridges/Tigervision.hpp @@ -13,25 +13,28 @@ namespace Atari2600::Cartridge { class Tigervision: public BusExtender { - public: - Tigervision(uint8_t *rom_base, std::size_t rom_size) : - BusExtender(rom_base, rom_size) { - rom_ptr_[0] = rom_base + rom_size - 4096; - rom_ptr_[1] = rom_ptr_[0] + 2048; - } +public: + Tigervision(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) { + rom_ptr_[0] = rom_base + rom_size - 4096; + rom_ptr_[1] = rom_ptr_[0] + 2048; + } - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - if((address&0x1fff) == 0x3f) { - int offset = ((*value) * 2048) & (rom_size_ - 1); - rom_ptr_[0] = rom_base_ + offset; - return; - } else if((address&0x1000) && isReadOperation(operation)) { - *value = rom_ptr_[(address >> 11)&1][address & 2047]; - } + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + const uint16_t address, + uint8_t *const value + ) { + if((address&0x1fff) == 0x3f) { + int offset = ((*value) * 2048) & (rom_size_ - 1); + rom_ptr_[0] = rom_base_ + offset; + return; + } else if((address&0x1000) && isReadOperation(operation)) { + *value = rom_ptr_[(address >> 11)&1][address & 2047]; } + } - private: - uint8_t *rom_ptr_[2]; +private: + const uint8_t *rom_ptr_[2]; }; } diff --git a/Machines/Atari/2600/Cartridges/Unpaged.hpp b/Machines/Atari/2600/Cartridges/Unpaged.hpp index f778d3f1b..0ad858a30 100644 --- a/Machines/Atari/2600/Cartridges/Unpaged.hpp +++ b/Machines/Atari/2600/Cartridges/Unpaged.hpp @@ -13,14 +13,18 @@ namespace Atari2600::Cartridge { class Unpaged: public BusExtender { - public: - Unpaged(uint8_t *rom_base, std::size_t rom_size) : BusExtender(rom_base, rom_size) {} +public: + Unpaged(const uint8_t *const rom_base, const std::size_t rom_size) : BusExtender(rom_base, rom_size) {} - void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - if(isReadOperation(operation) && (address & 0x1000)) { - *value = rom_base_[address & (rom_size_ - 1)]; - } + void perform_bus_operation( + const CPU::MOS6502::BusOperation operation, + const uint16_t address, + uint8_t *const value + ) { + if(isReadOperation(operation) && (address & 0x1000)) { + *value = rom_base_[address & (rom_size_ - 1)]; } + } }; } diff --git a/Machines/Atari/2600/PIA.hpp b/Machines/Atari/2600/PIA.hpp index b25cf52d5..1a3832d04 100644 --- a/Machines/Atari/2600/PIA.hpp +++ b/Machines/Atari/2600/PIA.hpp @@ -15,23 +15,20 @@ namespace Atari2600 { class PIA: public MOS::MOS6532 { - public: - inline uint8_t get_port_input(int port) { - return port_values_[port]; - } +public: + inline uint8_t get_port_input(const int port) { + return port_values_[port]; + } - inline void update_port_input(int port, uint8_t mask, bool set) { - if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask; - set_port_did_change(port); - } + inline void update_port_input(const int port, const uint8_t mask, const bool set) { + if(set) port_values_[port] &= ~mask; else port_values_[port] |= mask; + set_port_did_change(port); + } - PIA() : - port_values_{0xff, 0xff} - {} - - private: - uint8_t port_values_[2]; + PIA() : port_values_{0xff, 0xff} {} +private: + uint8_t port_values_[2]; }; } diff --git a/Machines/Atari/2600/TIA.hpp b/Machines/Atari/2600/TIA.hpp index 71ba63286..71377ef1e 100644 --- a/Machines/Atari/2600/TIA.hpp +++ b/Machines/Atari/2600/TIA.hpp @@ -19,296 +19,295 @@ namespace Atari2600 { class TIA { - public: - TIA(); +public: + TIA(); - enum class OutputMode { - NTSC, PAL - }; + enum class OutputMode { + NTSC, PAL + }; - /*! - Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed. - */ - void run_for(const Cycles cycles); - void set_output_mode(OutputMode output_mode); + /*! + Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed. + */ + void run_for(const Cycles cycles); + void set_output_mode(OutputMode output_mode); - void set_sync(bool sync); - void set_blank(bool blank); - void reset_horizontal_counter(); // Reset is delayed by four cycles. + void set_sync(bool sync); + void set_blank(bool blank); + void reset_horizontal_counter(); // Reset is delayed by four cycles. - /*! - @returns the number of cycles between (current TIA time) + from_offset to the current or - next horizontal blanking period. Returns numbers in the range [0, 227]. - */ - int get_cycles_until_horizontal_blank(const Cycles from_offset); + /*! + @returns the number of cycles between (current TIA time) + from_offset to the current or + next horizontal blanking period. Returns numbers in the range [0, 227]. + */ + int get_cycles_until_horizontal_blank(const Cycles from_offset); - void set_background_colour(uint8_t colour); + void set_background_colour(uint8_t colour); - void set_playfield(uint16_t offset, uint8_t value); - void set_playfield_control_and_ball_size(uint8_t value); - void set_playfield_ball_colour(uint8_t colour); + void set_playfield(uint16_t offset, uint8_t value); + void set_playfield_control_and_ball_size(uint8_t value); + void set_playfield_ball_colour(uint8_t colour); - void set_player_number_and_size(int player, uint8_t value); - void set_player_graphic(int player, uint8_t value); - void set_player_reflected(int player, bool reflected); - void set_player_delay(int player, bool delay); - void set_player_position(int player); - void set_player_motion(int player, uint8_t motion); - void set_player_missile_colour(int player, uint8_t colour); + void set_player_number_and_size(int player, uint8_t value); + void set_player_graphic(int player, uint8_t value); + void set_player_reflected(int player, bool reflected); + void set_player_delay(int player, bool delay); + void set_player_position(int player); + void set_player_motion(int player, uint8_t motion); + void set_player_missile_colour(int player, uint8_t colour); - void set_missile_enable(int missile, bool enabled); - void set_missile_position(int missile); - void set_missile_position_to_player(int missile, bool lock); - void set_missile_motion(int missile, uint8_t motion); + void set_missile_enable(int missile, bool enabled); + void set_missile_position(int missile); + void set_missile_position_to_player(int missile, bool lock); + void set_missile_motion(int missile, uint8_t motion); - void set_ball_enable(bool enabled); - void set_ball_delay(bool delay); - void set_ball_position(); - void set_ball_motion(uint8_t motion); + void set_ball_enable(bool enabled); + void set_ball_delay(bool delay); + void set_ball_position(); + void set_ball_motion(uint8_t motion); - void move(); - void clear_motion(); + void move(); + void clear_motion(); - uint8_t get_collision_flags(int offset); - void clear_collision_flags(); + uint8_t get_collision_flags(int offset); + void clear_collision_flags(); - void set_crt_delegate(Outputs::CRT::Delegate *); - void set_scan_target(Outputs::Display::ScanTarget *); - Outputs::Display::ScanStatus get_scaled_scan_status() const; + void set_crt_delegate(Outputs::CRT::Delegate *); + void set_scan_target(Outputs::Display::ScanTarget *); + Outputs::Display::ScanStatus get_scaled_scan_status() const; + +private: + Outputs::CRT::CRT crt_; + + // the master counter; counts from 0 to 228 with all visible pixels being in the final 160 + int horizontal_counter_ = 0; + + // contains flags to indicate whether sync or blank are currently active + int output_mode_ = 0; + + // keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer + alignas(alignof(uint32_t)) uint8_t collision_buffer_[160]; + enum class CollisionType : uint8_t { + Playfield = (1 << 0), + Ball = (1 << 1), + Player0 = (1 << 2), + Player1 = (1 << 3), + Missile0 = (1 << 4), + Missile1 = (1 << 5) + }; + + int collision_flags_ = 0; + int collision_flags_by_buffer_vaules_[64]; + + // colour mapping tables + enum class ColourMode { + Standard = 0, + ScoreLeft, + ScoreRight, + OnTop + }; + uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry + + enum class ColourIndex { + Background = 0, + PlayfieldBall, + PlayerMissile0, + PlayerMissile1 + }; + struct Colour { + uint16_t luminance_phase; + uint8_t original; + }; + std::array colour_palette_; + void set_colour_palette_entry(size_t index, uint8_t colour); + OutputMode tv_standard_; + + // playfield state + int background_half_mask_ = 0; + enum class PlayfieldPriority { + Standard, + Score, + OnTop + } playfield_priority_; + uint32_t background_[2] = {0, 0}; + // contains two 20-bit bitfields representing the background state; + // at index 0 is the left-hand side of the playfield with bit 0 being + // the first bit to display, bit 1 the second, etc. Index 1 contains + // a mirror image of index 0. If the playfield is being displayed in + // mirroring mode, background_[0] will be output on the left and + // background_[1] on the right; otherwise background_[0] will be + // output twice. + + // objects + template struct Object { + // the two programmer-set values + int position = 0; + int motion = 0; + + // motion_step_ is the current motion counter value; motion_time_ is the next time it will fire + int motion_step = 0; + int motion_time = 0; + + // indicates whether this object is currently undergoing motion + bool is_moving = false; + }; + + // player state + struct Player: public Object { + int adder = 4; + int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ + uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current + int reverse_mask = false; // 7 for a reflected player, 0 for normal + int graphic_index = 0; + + int pixel_position = 32, pixel_counter = 0; + int latched_pixel4_time = -1; + const bool enqueues = true; + + inline void skip_pixels(const int count, int from_horizontal_counter) { + int old_pixel_counter = pixel_counter; + pixel_position = std::min(32, pixel_position + count * adder); + pixel_counter += count; + if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) { + latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter; + } + } + + inline void reset_pixels(int copy) { + pixel_position = pixel_counter = 0; + copy_index_ = copy; + } + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { + output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask); + skip_pixels(count, from_horizontal_counter); + } + + void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) { + while(queue_read_pointer_ != queue_write_pointer_) { + uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start]; + if(queue_[queue_read_pointer_].end > time_now) { + const int length = time_now - queue_[queue_read_pointer_].start; + output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); + queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder; + queue_[queue_read_pointer_].start = time_now; + return; + } else { + output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); + } + queue_read_pointer_ = (queue_read_pointer_ + 1)&3; + } + } + + void enqueue_pixels(const int start, const int end, int from_horizontal_counter) { + queue_[queue_write_pointer_].start = start; + queue_[queue_write_pointer_].end = end; + queue_[queue_write_pointer_].pixel_position = pixel_position; + queue_[queue_write_pointer_].adder = adder; + queue_[queue_write_pointer_].reverse_mask = reverse_mask; + queue_write_pointer_ = (queue_write_pointer_ + 1)&3; + skip_pixels(end - start, from_horizontal_counter); + } private: - Outputs::CRT::CRT crt_; + int copy_index_ = 0; + struct QueuedPixels { + int start = 0, end = 0; + int pixel_position = 0; + int adder = 0; + int reverse_mask = false; + } queue_[4]; + int queue_read_pointer_ = 0, queue_write_pointer_ = 0; - // the master counter; counts from 0 to 228 with all visible pixels being in the final 160 - int horizontal_counter_ = 0; - - // contains flags to indicate whether sync or blank are currently active - int output_mode_ = 0; - - // keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer - alignas(alignof(uint32_t)) uint8_t collision_buffer_[160]; - enum class CollisionType : uint8_t { - Playfield = (1 << 0), - Ball = (1 << 1), - Player0 = (1 << 2), - Player1 = (1 << 3), - Missile0 = (1 << 4), - Missile1 = (1 << 5) - }; - - int collision_flags_ = 0; - int collision_flags_by_buffer_vaules_[64]; - - // colour mapping tables - enum class ColourMode { - Standard = 0, - ScoreLeft, - ScoreRight, - OnTop - }; - uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry - - enum class ColourIndex { - Background = 0, - PlayfieldBall, - PlayerMissile0, - PlayerMissile1 - }; - struct Colour { - uint16_t luminance_phase; - uint8_t original; - }; - std::array colour_palette_; - void set_colour_palette_entry(size_t index, uint8_t colour); - OutputMode tv_standard_; - - // playfield state - int background_half_mask_ = 0; - enum class PlayfieldPriority { - Standard, - Score, - OnTop - } playfield_priority_; - uint32_t background_[2] = {0, 0}; - // contains two 20-bit bitfields representing the background state; - // at index 0 is the left-hand side of the playfield with bit 0 being - // the first bit to display, bit 1 the second, etc. Index 1 contains - // a mirror image of index 0. If the playfield is being displayed in - // mirroring mode, background_[0] will be output on the left and - // background_[1] on the right; otherwise background_[0] will be - // output twice. - - // objects - template struct Object { - // the two programmer-set values - int position = 0; - int motion = 0; - - // motion_step_ is the current motion counter value; motion_time_ is the next time it will fire - int motion_step = 0; - int motion_time = 0; - - // indicates whether this object is currently undergoing motion - bool is_moving = false; - }; - - // player state - struct Player: public Object { - int adder = 4; - int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ - uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current - int reverse_mask = false; // 7 for a reflected player, 0 for normal - int graphic_index = 0; - - int pixel_position = 32, pixel_counter = 0; - int latched_pixel4_time = -1; - const bool enqueues = true; - - inline void skip_pixels(const int count, int from_horizontal_counter) { - int old_pixel_counter = pixel_counter; - pixel_position = std::min(32, pixel_position + count * adder); - pixel_counter += count; - if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) { - latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter; - } + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) { + if(output_pixel_position == 32 || !graphic[graphic_index]) return; + int output_cursor = 0; + while(output_pixel_position < 32 && output_cursor < count) { + int shift = (output_pixel_position >> 2) ^ output_reverse_mask; + target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity; + output_cursor++; + output_pixel_position += output_adder; } + } + } player_[2]; - inline void reset_pixels(int copy) { - pixel_position = pixel_counter = 0; - copy_index_ = copy; + // common actor for things that appear as a horizontal run of pixels + struct HorizontalRun: public Object { + int pixel_position = 0; + int size = 1; + const bool enqueues = false; + + inline void skip_pixels(const int count, int) { + pixel_position = std::max(0, pixel_position - count); + } + + inline void reset_pixels(int) { + pixel_position = size; + } + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, [[maybe_unused]] int from_horizontal_counter) { + int output_cursor = 0; + while(pixel_position && output_cursor < count) + { + target[output_cursor] |= collision_identity; + output_cursor++; + pixel_position--; } + } - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { - output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask); + void dequeue_pixels([[maybe_unused]] uint8_t *const target, [[maybe_unused]] uint8_t collision_identity, [[maybe_unused]] int time_now) {} + void enqueue_pixels([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] int from_horizontal_counter) {} + }; + + // missile state + struct Missile: public HorizontalRun { + bool enabled = false; + bool locked_to_player = false; + int copy_flags = 0; + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { + if(!pixel_position) return; + if(enabled && !locked_to_player) { + HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); + } else { skip_pixels(count, from_horizontal_counter); } + } + } missile_[2]; - void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) { - while(queue_read_pointer_ != queue_write_pointer_) { - uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start]; - if(queue_[queue_read_pointer_].end > time_now) { - const int length = time_now - queue_[queue_read_pointer_].start; - output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); - queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder; - queue_[queue_read_pointer_].start = time_now; - return; - } else { - output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); - } - queue_read_pointer_ = (queue_read_pointer_ + 1)&3; - } + // ball state + struct Ball: public HorizontalRun { + bool enabled[2] = {false, false}; + int enabled_index = 0; + const int copy_flags = 0; + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { + if(!pixel_position) return; + if(enabled[enabled_index]) { + HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); + } else { + skip_pixels(count, from_horizontal_counter); } + } + } ball_; - void enqueue_pixels(const int start, const int end, int from_horizontal_counter) { - queue_[queue_write_pointer_].start = start; - queue_[queue_write_pointer_].end = end; - queue_[queue_write_pointer_].pixel_position = pixel_position; - queue_[queue_write_pointer_].adder = adder; - queue_[queue_write_pointer_].reverse_mask = reverse_mask; - queue_write_pointer_ = (queue_write_pointer_ + 1)&3; - skip_pixels(end - start, from_horizontal_counter); - } + // motion + bool horizontal_blank_extend_ = false; + template void perform_border_motion(T &object, int start, int end); + template void perform_motion_step(T &object); - private: - int copy_index_ = 0; - struct QueuedPixels { - int start = 0, end = 0; - int pixel_position = 0; - int adder = 0; - int reverse_mask = false; - } queue_[4]; - int queue_read_pointer_ = 0, queue_write_pointer_ = 0; + // drawing methods and state + void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end); + template void draw_object(T &, const uint8_t collision_identity, int start, int end); + template void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now); + inline void draw_playfield(int start, int end); - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) { - if(output_pixel_position == 32 || !graphic[graphic_index]) return; - int output_cursor = 0; - while(output_pixel_position < 32 && output_cursor < count) { - int shift = (output_pixel_position >> 2) ^ output_reverse_mask; - target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity; - output_cursor++; - output_pixel_position += output_adder; - } - } + inline void output_for_cycles(int number_of_cycles); + inline void output_line(); - } player_[2]; - - // common actor for things that appear as a horizontal run of pixels - struct HorizontalRun: public Object { - int pixel_position = 0; - int size = 1; - const bool enqueues = false; - - inline void skip_pixels(const int count, int) { - pixel_position = std::max(0, pixel_position - count); - } - - inline void reset_pixels(int) { - pixel_position = size; - } - - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, [[maybe_unused]] int from_horizontal_counter) { - int output_cursor = 0; - while(pixel_position && output_cursor < count) - { - target[output_cursor] |= collision_identity; - output_cursor++; - pixel_position--; - } - } - - void dequeue_pixels([[maybe_unused]] uint8_t *const target, [[maybe_unused]] uint8_t collision_identity, [[maybe_unused]] int time_now) {} - void enqueue_pixels([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] int from_horizontal_counter) {} - }; - - // missile state - struct Missile: public HorizontalRun { - bool enabled = false; - bool locked_to_player = false; - int copy_flags = 0; - - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { - if(!pixel_position) return; - if(enabled && !locked_to_player) { - HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); - } else { - skip_pixels(count, from_horizontal_counter); - } - } - } missile_[2]; - - // ball state - struct Ball: public HorizontalRun { - bool enabled[2] = {false, false}; - int enabled_index = 0; - const int copy_flags = 0; - - inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) { - if(!pixel_position) return; - if(enabled[enabled_index]) { - HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); - } else { - skip_pixels(count, from_horizontal_counter); - } - } - } ball_; - - // motion - bool horizontal_blank_extend_ = false; - template void perform_border_motion(T &object, int start, int end); - template void perform_motion_step(T &object); - - // drawing methods and state - void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end); - template void draw_object(T &, const uint8_t collision_identity, int start, int end); - template void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now); - inline void draw_playfield(int start, int end); - - inline void output_for_cycles(int number_of_cycles); - inline void output_line(); - - int pixels_start_location_ = 0; - uint16_t *pixel_target_ = nullptr; - inline void output_pixels(int start, int end); + int pixels_start_location_ = 0; + uint16_t *pixel_target_ = nullptr; + inline void output_pixels(int start, int end); }; } diff --git a/Machines/Atari/2600/TIASound.hpp b/Machines/Atari/2600/TIASound.hpp index a71a16e9e..36ffcc4e3 100644 --- a/Machines/Atari/2600/TIASound.hpp +++ b/Machines/Atari/2600/TIASound.hpp @@ -18,32 +18,32 @@ namespace Atari2600 { constexpr int CPUTicksPerAudioTick = 2; class TIASound: public Outputs::Speaker::BufferSource { - public: - TIASound(Concurrency::AsyncTaskQueue &audio_queue); +public: + TIASound(Concurrency::AsyncTaskQueue &); - void set_volume(int channel, uint8_t volume); - void set_divider(int channel, uint8_t divider); - void set_control(int channel, uint8_t control); + void set_volume(int channel, uint8_t volume); + void set_divider(int channel, uint8_t divider); + void set_control(int channel, uint8_t control); - // To satisfy ::SampleSource. - template - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); - void set_sample_volume_range(std::int16_t range); + // To satisfy ::SampleSource. + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *); + void set_sample_volume_range(std::int16_t range); - private: - Concurrency::AsyncTaskQueue &audio_queue_; +private: + Concurrency::AsyncTaskQueue &audio_queue_; - uint8_t volume_[2]; - uint8_t divider_[2]; - uint8_t control_[2]; + uint8_t volume_[2]; + uint8_t divider_[2]; + uint8_t control_[2]; - int poly4_counter_[2]; - int poly5_counter_[2]; - int poly9_counter_[2]; - int output_state_[2]; + int poly4_counter_[2]; + int poly5_counter_[2]; + int poly9_counter_[2]; + int output_state_[2]; - int divider_counter_[2]; - int16_t per_channel_volume_ = 0; + int divider_counter_[2]; + int16_t per_channel_volume_ = 0; }; } diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index e45515a62..9551b817f 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -34,75 +34,75 @@ namespace Coleco { namespace Vision { class Joystick: public Inputs::ConcreteJoystick { - public: - Joystick() : - ConcreteJoystick({ - Input(Input::Up), - Input(Input::Down), - Input(Input::Left), - Input(Input::Right), +public: + Joystick() : + ConcreteJoystick({ + Input(Input::Up), + Input(Input::Down), + Input(Input::Left), + Input(Input::Right), - Input(Input::Fire, 0), - Input(Input::Fire, 1), + Input(Input::Fire, 0), + Input(Input::Fire, 1), - Input('0'), Input('1'), Input('2'), - Input('3'), Input('4'), Input('5'), - Input('6'), Input('7'), Input('8'), - Input('9'), Input('*'), Input('#'), - }) {} + Input('0'), Input('1'), Input('2'), + Input('3'), Input('4'), Input('5'), + Input('6'), Input('7'), Input('8'), + Input('9'), Input('*'), Input('#'), + }) {} - void did_set_input(const Input &digital_input, bool is_active) final { - switch(digital_input.type) { - default: return; + void did_set_input(const Input &digital_input, bool is_active) final { + switch(digital_input.type) { + default: return; - case Input::Key: - if(!is_active) keypad_ |= 0xf; - else { - uint8_t mask = 0xf; - switch(digital_input.info.key.symbol) { - case '8': mask = 0x1; break; - case '4': mask = 0x2; break; - case '5': mask = 0x3; break; - case '7': mask = 0x5; break; - case '#': mask = 0x6; break; - case '2': mask = 0x7; break; - case '*': mask = 0x9; break; - case '0': mask = 0xa; break; - case '9': mask = 0xb; break; - case '3': mask = 0xc; break; - case '1': mask = 0xd; break; - case '6': mask = 0xe; break; - default: break; - } - keypad_ = (keypad_ & 0xf0) | mask; - } - break; - - case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break; - case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break; - case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break; - case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break; - case Input::Fire: - switch(digital_input.info.control.index) { + case Input::Key: + if(!is_active) keypad_ |= 0xf; + else { + uint8_t mask = 0xf; + switch(digital_input.info.key.symbol) { + case '8': mask = 0x1; break; + case '4': mask = 0x2; break; + case '5': mask = 0x3; break; + case '7': mask = 0x5; break; + case '#': mask = 0x6; break; + case '2': mask = 0x7; break; + case '*': mask = 0x9; break; + case '0': mask = 0xa; break; + case '9': mask = 0xb; break; + case '3': mask = 0xc; break; + case '1': mask = 0xd; break; + case '6': mask = 0xe; break; default: break; - case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break; - case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break; } - break; - } - } + keypad_ = (keypad_ & 0xf0) | mask; + } + break; - uint8_t get_direction_input() { - return direction_; + case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break; + case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break; + case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break; + case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break; + case Input::Fire: + switch(digital_input.info.control.index) { + default: break; + case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break; + case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break; + } + break; } + } - uint8_t get_keypad_input() { - return keypad_; - } + uint8_t get_direction_input() { + return direction_; + } - private: - uint8_t direction_ = 0xff; - uint8_t keypad_ = 0x7f; + uint8_t get_keypad_input() { + return keypad_; + } + +private: + uint8_t direction_ = 0xff; + uint8_t keypad_ = 0x7f; }; class ConcreteMachine: @@ -114,297 +114,297 @@ class ConcreteMachine: public MachineTypes::AudioProducer, public MachineTypes::JoystickMachine { - public: - ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - z80_(*this), - sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), - ay_(GI::AY38910::Personality::AY38910, audio_queue_), - mixer_(sn76489_, ay_), - speaker_(mixer_) { - speaker_.set_input_rate(3579545.0f / float(sn76489_divider)); - set_clock_rate(3579545); - joysticks_.emplace_back(new Joystick); - joysticks_.emplace_back(new Joystick); +public: + ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + z80_(*this), + sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), + ay_(GI::AY38910::Personality::AY38910, audio_queue_), + mixer_(sn76489_, ay_), + speaker_(mixer_) { + speaker_.set_input_rate(3579545.0f / float(sn76489_divider)); + set_clock_rate(3579545); + joysticks_.emplace_back(new Joystick); + joysticks_.emplace_back(new Joystick); - constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS; - const ROM::Request request(rom_name); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; + constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS; + const ROM::Request request(rom_name); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + bios_ = roms.find(rom_name)->second; + + if(!target.media.cartridges.empty()) { + const auto &segment = target.media.cartridges.front()->get_segments().front(); + cartridge_ = segment.data; + if(cartridge_.size() >= 32768) + cartridge_address_limit_ = 0xffff; + else + cartridge_address_limit_ = uint16_t(0x8000 + cartridge_.size() - 1); + + if(cartridge_.size() > 32768) { + // Ensure the cartrige is a multiple of 16kb in size, as that won't + // be checked when paging. + const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384; + cartridge_.resize(cartridge_.size() + extension); + + cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384]; + cartridge_pages_[1] = cartridge_.data(); + is_megacart_ = true; + } else { + // Ensure at least 32kb is allocated to the cartrige so that + // reads are never out of bounds. + cartridge_.resize(32768); + + cartridge_pages_[0] = cartridge_.data(); + cartridge_pages_[1] = cartridge_.data() + 16384; + is_megacart_ = false; } - bios_ = roms.find(rom_name)->second; - - if(!target.media.cartridges.empty()) { - const auto &segment = target.media.cartridges.front()->get_segments().front(); - cartridge_ = segment.data; - if(cartridge_.size() >= 32768) - cartridge_address_limit_ = 0xffff; - else - cartridge_address_limit_ = uint16_t(0x8000 + cartridge_.size() - 1); - - if(cartridge_.size() > 32768) { - // Ensure the cartrige is a multiple of 16kb in size, as that won't - // be checked when paging. - const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384; - cartridge_.resize(cartridge_.size() + extension); - - cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384]; - cartridge_pages_[1] = cartridge_.data(); - is_megacart_ = true; - } else { - // Ensure at least 32kb is allocated to the cartrige so that - // reads are never out of bounds. - cartridge_.resize(32768); - - cartridge_pages_[0] = cartridge_.data(); - cartridge_pages_[1] = cartridge_.data() + 16384; - is_megacart_ = false; - } - } - - // ColecoVisions have composite output only. - vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour); } - ~ConcreteMachine() { - audio_queue_.flush(); + // ColecoVisions have composite output only. + vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + const std::vector> &get_joysticks() final { + return joysticks_; + } + + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + vdp_.last_valid()->set_scan_target(scan_target); + } + + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return vdp_.last_valid()->get_scaled_scan_status(); + } + + void set_display_type(Outputs::Display::DisplayType display_type) final { + vdp_.last_valid()->set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return vdp_.last_valid()->get_display_type(); + } + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + void run_for(const Cycles cycles) final { + z80_.run_for(cycles); + } + + // MARK: Z80::BusHandler + forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + // The SN76489 will use its ready line to trigger the Z80's wait, which will add + // thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle. + // This code works out the delay up front in order to simplify execution flow, though + // technically this is a little duplicative. + HalfCycles penalty(0); + if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) { + penalty = HalfCycles(62); + } else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) { + penalty = HalfCycles(2); } + const HalfCycles length = cycle.length + penalty; - const std::vector> &get_joysticks() final { - return joysticks_; + if(vdp_ += length) { + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); } + time_since_sn76489_update_ += length; - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - vdp_.last_valid()->set_scan_target(scan_target); - } - - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return vdp_.last_valid()->get_scaled_scan_status(); - } - - void set_display_type(Outputs::Display::DisplayType display_type) final { - vdp_.last_valid()->set_display_type(display_type); - } - - Outputs::Display::DisplayType get_display_type() const final { - return vdp_.last_valid()->get_display_type(); - } - - Outputs::Speaker::Speaker *get_speaker() final { - return &speaker_; - } - - void run_for(const Cycles cycles) final { - z80_.run_for(cycles); - } - - // MARK: Z80::BusHandler - forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - // The SN76489 will use its ready line to trigger the Z80's wait, which will add - // thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle. - // This code works out the delay up front in order to simplify execution flow, though - // technically this is a little duplicative. - HalfCycles penalty(0); - if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) { - penalty = HalfCycles(62); - } else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) { - penalty = HalfCycles(2); - } - const HalfCycles length = cycle.length + penalty; - - if(vdp_ += length) { - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - } - time_since_sn76489_update_ += length; - - // Act only if necessary. - if(cycle.is_terminal()) { - uint16_t address = cycle.address ? *cycle.address : 0x0000; - switch(cycle.operation) { - case CPU::Z80::PartialMachineCycle::ReadOpcode: - if(!address) pc_zero_accesses_++; - [[fallthrough]]; - case CPU::Z80::PartialMachineCycle::Read: - if(address < 0x2000) { - if(super_game_module_.replace_bios) { - *cycle.value = super_game_module_.ram[address]; - } else { - *cycle.value = bios_[address]; - } - } else if(super_game_module_.replace_ram && address < 0x8000) { + // Act only if necessary. + if(cycle.is_terminal()) { + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: + if(!address) pc_zero_accesses_++; + [[fallthrough]]; + case CPU::Z80::PartialMachineCycle::Read: + if(address < 0x2000) { + if(super_game_module_.replace_bios) { *cycle.value = super_game_module_.ram[address]; - } else if(address >= 0x6000 && address < 0x8000) { - *cycle.value = ram_[address & 1023]; - } else if(address >= 0x8000 && address <= cartridge_address_limit_) { - if(is_megacart_ && address >= 0xffc0) { - page_megacart(address); - } - *cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff]; } else { - *cycle.value = 0xff; + *cycle.value = bios_[address]; } - break; - - case CPU::Z80::PartialMachineCycle::Write: - if(super_game_module_.replace_bios && address < 0x2000) { - super_game_module_.ram[address] = *cycle.value; - } else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { - super_game_module_.ram[address] = *cycle.value; - } else if(address >= 0x6000 && address < 0x8000) { - ram_[address & 1023] = *cycle.value; - } else if(is_megacart_ && address >= 0xffc0) { + } else if(super_game_module_.replace_ram && address < 0x8000) { + *cycle.value = super_game_module_.ram[address]; + } else if(address >= 0x6000 && address < 0x8000) { + *cycle.value = ram_[address & 1023]; + } else if(address >= 0x8000 && address <= cartridge_address_limit_) { + if(is_megacart_ && address >= 0xffc0) { page_megacart(address); } - break; + *cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff]; + } else { + *cycle.value = 0xff; + } + break; - case CPU::Z80::PartialMachineCycle::Input: - switch((address >> 5) & 7) { - case 5: - *cycle.value = vdp_->read(address); - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - break; + case CPU::Z80::PartialMachineCycle::Write: + if(super_game_module_.replace_bios && address < 0x2000) { + super_game_module_.ram[address] = *cycle.value; + } else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { + super_game_module_.ram[address] = *cycle.value; + } else if(address >= 0x6000 && address < 0x8000) { + ram_[address & 1023] = *cycle.value; + } else if(is_megacart_ && address >= 0xffc0) { + page_megacart(address); + } + break; - case 7: { - const std::size_t joystick_id = (address&2) >> 1; - Joystick *joystick = static_cast(joysticks_[joystick_id].get()); - if(joysticks_in_keypad_mode_) { - *cycle.value = joystick->get_keypad_input(); - } else { - *cycle.value = joystick->get_direction_input(); - } + case CPU::Z80::PartialMachineCycle::Input: + switch((address >> 5) & 7) { + case 5: + *cycle.value = vdp_->read(address); + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); + break; - // Hitting exactly the recommended joypad input port is an indicator that - // this really is a ColecoVision game. The BIOS won't do this when just waiting - // to start a game (unlike accessing the VDP and SN). - if((address&0xfc) == 0xfc) confidence_counter_.add_hit(); - } break; + case 7: { + const std::size_t joystick_id = (address&2) >> 1; + Joystick *joystick = static_cast(joysticks_[joystick_id].get()); + if(joysticks_in_keypad_mode_) { + *cycle.value = joystick->get_keypad_input(); + } else { + *cycle.value = joystick->get_direction_input(); + } - default: - switch(address&0xff) { - default: *cycle.value = 0xff; break; - case 0x52: - // Read AY data. - update_audio(); - *cycle.value = GI::AY38910::Utility::read(ay_); - break; - } - break; - } - break; + // Hitting exactly the recommended joypad input port is an indicator that + // this really is a ColecoVision game. The BIOS won't do this when just waiting + // to start a game (unlike accessing the VDP and SN). + if((address&0xfc) == 0xfc) confidence_counter_.add_hit(); + } break; - case CPU::Z80::PartialMachineCycle::Output: { - const int eighth = (address >> 5) & 7; - switch(eighth) { - case 4: case 6: - joysticks_in_keypad_mode_ = eighth == 4; - break; + default: + switch(address&0xff) { + default: *cycle.value = 0xff; break; + case 0x52: + // Read AY data. + update_audio(); + *cycle.value = GI::AY38910::Utility::read(ay_); + break; + } + break; + } + break; - case 5: - vdp_->write(address, *cycle.value); - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - break; + case CPU::Z80::PartialMachineCycle::Output: { + const int eighth = (address >> 5) & 7; + switch(eighth) { + case 4: case 6: + joysticks_in_keypad_mode_ = eighth == 4; + break; - case 7: - update_audio(); - sn76489_.write(*cycle.value); - break; + case 5: + vdp_->write(address, *cycle.value); + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); + break; - default: - // Catch Super Game Module accesses; it decodes more thoroughly. - switch(address&0xff) { - default: break; - case 0x7f: - super_game_module_.replace_bios = !((*cycle.value)&0x2); - break; - case 0x50: - // Set AY address. - update_audio(); - GI::AY38910::Utility::select_register(ay_, *cycle.value); - break; - case 0x51: - // Set AY data. - update_audio(); - GI::AY38910::Utility::write_data(ay_, *cycle.value); - break; - case 0x53: - super_game_module_.replace_ram = !!((*cycle.value)&0x1); - break; - } - break; - } - } break; + case 7: + update_audio(); + sn76489_.write(*cycle.value); + break; - default: break; - } - } + default: + // Catch Super Game Module accesses; it decodes more thoroughly. + switch(address&0xff) { + default: break; + case 0x7f: + super_game_module_.replace_bios = !((*cycle.value)&0x2); + break; + case 0x50: + // Set AY address. + update_audio(); + GI::AY38910::Utility::select_register(ay_, *cycle.value); + break; + case 0x51: + // Set AY data. + update_audio(); + GI::AY38910::Utility::write_data(ay_, *cycle.value); + break; + case 0x53: + super_game_module_.replace_ram = !!((*cycle.value)&0x1); + break; + } + break; + } + } break; - return penalty; - } - - void flush_output(int outputs) final { - if(outputs & Output::Video) { - vdp_.flush(); - } - if(outputs & Output::Audio) { - update_audio(); - audio_queue_.perform(); + default: break; } } - float get_confidence() final { - if(pc_zero_accesses_ > 1) return 0.0f; - return confidence_counter_.get_confidence(); + return penalty; + } + + void flush_output(int outputs) final { + if(outputs & Output::Video) { + vdp_.flush(); } - - // MARK: - Configuration options. - std::unique_ptr get_options() const final { - auto options = std::make_unique(Configurable::OptionsType::UserFriendly); - options->output = get_video_signal_configurable(); - return options; + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); } + } - void set_options(const std::unique_ptr &str) final { - const auto options = dynamic_cast(str.get()); - set_video_signal_configurable(options->output); - } + float get_confidence() final { + if(pc_zero_accesses_ > 1) return 0.0f; + return confidence_counter_.get_confidence(); + } - private: - inline void page_megacart(uint16_t address) { - const std::size_t selected_start = (size_t(address&63) << 14) % cartridge_.size(); - cartridge_pages_[1] = &cartridge_[selected_start]; - } - inline void update_audio() { - speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); - } + // MARK: - Configuration options. + std::unique_ptr get_options() const final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; + } - CPU::Z80::Processor z80_; - JustInTimeActor> vdp_; + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); + } - Concurrency::AsyncTaskQueue audio_queue_; - TI::SN76489 sn76489_; - GI::AY38910::AY38910 ay_; - Outputs::Speaker::CompoundSource> mixer_; - Outputs::Speaker::PullLowpass>> speaker_; +private: + inline void page_megacart(uint16_t address) { + const std::size_t selected_start = (size_t(address&63) << 14) % cartridge_.size(); + cartridge_pages_[1] = &cartridge_[selected_start]; + } + inline void update_audio() { + speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); + } - std::vector bios_; - std::vector cartridge_; - uint8_t *cartridge_pages_[2]; - uint8_t ram_[1024]; - bool is_megacart_ = false; - uint16_t cartridge_address_limit_ = 0; - struct { - bool replace_bios = false; - bool replace_ram = false; - uint8_t ram[32768]; - } super_game_module_; + CPU::Z80::Processor z80_; + JustInTimeActor> vdp_; - std::vector> joysticks_; - bool joysticks_in_keypad_mode_ = false; + Concurrency::AsyncTaskQueue audio_queue_; + TI::SN76489 sn76489_; + GI::AY38910::AY38910 ay_; + Outputs::Speaker::CompoundSource> mixer_; + Outputs::Speaker::PullLowpass>> speaker_; - HalfCycles time_since_sn76489_update_; + std::vector bios_; + std::vector cartridge_; + uint8_t *cartridge_pages_[2]; + uint8_t ram_[1024]; + bool is_megacart_ = false; + uint16_t cartridge_address_limit_ = 0; + struct { + bool replace_bios = false; + bool replace_ram = false; + uint8_t ram[32768]; + } super_game_module_; - Analyser::Dynamic::ConfidenceCounter confidence_counter_; - int pc_zero_accesses_ = 0; + std::vector> joysticks_; + bool joysticks_in_keypad_mode_ = false; + + HalfCycles time_since_sn76489_update_; + + Analyser::Dynamic::ConfidenceCounter confidence_counter_; + int pc_zero_accesses_ = 0; }; } diff --git a/Machines/KeyboardMachine.hpp b/Machines/KeyboardMachine.hpp index ee313fc8e..dee9d2a5e 100644 --- a/Machines/KeyboardMachine.hpp +++ b/Machines/KeyboardMachine.hpp @@ -112,43 +112,43 @@ class KeyboardMachine: public KeyActions { allowing automatic mapping from keyboard inputs to KeyActions. */ class MappedKeyboardMachine: public Inputs::Keyboard::Delegate, public KeyboardMachine { - public: - MappedKeyboardMachine(const std::set &essential_modifiers = {}); +public: + MappedKeyboardMachine(const std::set &essential_modifiers = {}); - /*! - A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys. - See the character mapper for logical mapping. - */ - class KeyboardMapper { - public: - virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const = 0; - }; + /*! + A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys. + See the character mapper for logical mapping. + */ + class KeyboardMapper { + public: + virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const = 0; + }; - /// Terminates a key sequence from the character mapper. - static constexpr uint16_t KeyEndSequence = 0xffff; + /// Terminates a key sequence from the character mapper. + static constexpr uint16_t KeyEndSequence = 0xffff; - /*! - Indicates that a key is not mapped (for the keyboard mapper) or that a - character cannot be typed (for the character mapper). - */ - static constexpr uint16_t KeyNotMapped = 0xfffe; + /*! + Indicates that a key is not mapped (for the keyboard mapper) or that a + character cannot be typed (for the character mapper). + */ + static constexpr uint16_t KeyNotMapped = 0xfffe; - /*! - Allows individual machines to provide the mapping between host keys - as per Inputs::Keyboard and their native scheme. - */ - virtual KeyboardMapper *get_keyboard_mapper(); + /*! + Allows individual machines to provide the mapping between host keys + as per Inputs::Keyboard and their native scheme. + */ + virtual KeyboardMapper *get_keyboard_mapper(); - /*! - Provides a keyboard that obtains this machine's keyboard mapper, maps - the key and supplies it via the KeyActions. - */ - virtual Inputs::Keyboard &get_keyboard() override; + /*! + Provides a keyboard that obtains this machine's keyboard mapper, maps + the key and supplies it via the KeyActions. + */ + virtual Inputs::Keyboard &get_keyboard() override; - private: - bool keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; - void reset_all_keys(Inputs::Keyboard *keyboard) override; - Inputs::Keyboard keyboard_; +private: + bool keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override; + void reset_all_keys(Inputs::Keyboard *keyboard) override; + Inputs::Keyboard keyboard_; }; } diff --git a/Machines/TimedMachine.hpp b/Machines/TimedMachine.hpp index 815297e12..d92da9b38 100644 --- a/Machines/TimedMachine.hpp +++ b/Machines/TimedMachine.hpp @@ -23,77 +23,77 @@ namespace MachineTypes { */ class TimedMachine { - public: - /// Runs the machine for @c duration seconds. - virtual void run_for(Time::Seconds duration) { - const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_; - clock_conversion_error_ = std::fmod(cycles, 1.0); - run_for(Cycles(int(cycles))); +public: + /// Runs the machine for @c duration seconds. + virtual void run_for(Time::Seconds duration) { + const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_; + clock_conversion_error_ = std::fmod(cycles, 1.0); + run_for(Cycles(int(cycles))); + } + + /*! + Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the + emulated machine to run 50% faster than a real machine. This speed-up is an emulation + fiction: it will apply across the system, including to the CRT. + */ + virtual void set_speed_multiplier(double multiplier) { + if(speed_multiplier_ == multiplier) { + return; } - /*! - Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the - emulated machine to run 50% faster than a real machine. This speed-up is an emulation - fiction: it will apply across the system, including to the CRT. - */ - virtual void set_speed_multiplier(double multiplier) { - if(speed_multiplier_ == multiplier) { - return; - } + speed_multiplier_ = multiplier; - speed_multiplier_ = multiplier; + auto audio_producer = dynamic_cast(this); + if(!audio_producer) return; - auto audio_producer = dynamic_cast(this); - if(!audio_producer) return; - - auto speaker = audio_producer->get_speaker(); - if(speaker) { - speaker->set_input_rate_multiplier(float(multiplier)); - } + auto speaker = audio_producer->get_speaker(); + if(speaker) { + speaker->set_input_rate_multiplier(float(multiplier)); } + } - /*! - @returns The current speed multiplier. - */ - virtual double get_speed_multiplier() const { - return speed_multiplier_; - } + /*! + @returns The current speed multiplier. + */ + virtual double get_speed_multiplier() const { + return speed_multiplier_; + } - /// @returns The confidence that this machine is running content it understands. - virtual float get_confidence() { return 0.5f; } - virtual std::string debug_type() { return ""; } + /// @returns The confidence that this machine is running content it understands. + virtual float get_confidence() { return 0.5f; } + virtual std::string debug_type() { return ""; } - struct Output { - static constexpr int Video = 1 << 0; - static constexpr int Audio = 1 << 1; + struct Output { + static constexpr int Video = 1 << 0; + static constexpr int Audio = 1 << 1; - static constexpr int All = Video | Audio; - }; - /// Ensures all locally-buffered output is posted onward for the types of output indicated - /// by the bitfield argument, which is comprised of flags from the namespace @c Output. - virtual void flush_output(int) {} + static constexpr int All = Video | Audio; + }; + /// Ensures all locally-buffered output is posted onward for the types of output indicated + /// by the bitfield argument, which is comprised of flags from the namespace @c Output. + virtual void flush_output(int) {} - protected: - /// Runs the machine for @c cycles. - virtual void run_for(const Cycles cycles) = 0; +protected: + /// Runs the machine for @c cycles. + virtual void run_for(const Cycles cycles) = 0; - /// Sets this machine's clock rate. - void set_clock_rate(double clock_rate) { - clock_rate_ = clock_rate; - } + /// Sets this machine's clock rate. + void set_clock_rate(double clock_rate) { + clock_rate_ = clock_rate; + } - /// Gets this machine's clock rate. - double get_clock_rate() const { - return clock_rate_; - } + /// Gets this machine's clock rate. + double get_clock_rate() const { + return clock_rate_; + } - private: - // Give the ScanProducer access to this machine's clock rate. - friend class ScanProducer; +private: + // Give the ScanProducer access to this machine's clock rate. + friend class ScanProducer; - double clock_rate_ = 1.0; - double clock_conversion_error_ = 0.0; - double speed_multiplier_ = 1.0; + double clock_rate_ = 1.0; + double clock_conversion_error_ = 0.0; + double speed_multiplier_ = 1.0; }; } diff --git a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm index c58f58b44..0a9add65e 100644 --- a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm +++ b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm @@ -112,8 +112,8 @@ struct TestProcessor: public CPU::MC68000::BusHandler { } } - private: - int instructions_remaining_; +private: + int instructions_remaining_; }; } diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index 26c651890..a79bb8de9 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -34,377 +34,377 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1" using Flags = InstructionSet::x86::Flags; struct Registers { - public: -// static constexpr bool is_32bit = false; +public: +// static constexpr bool is_32bit = false; - uint8_t &al() { return ax_.halves.low; } - uint8_t &ah() { return ax_.halves.high; } - uint16_t &ax() { return ax_.full; } + uint8_t &al() { return ax_.halves.low; } + uint8_t &ah() { return ax_.halves.high; } + uint16_t &ax() { return ax_.full; } - CPU::RegisterPair16 &axp() { return ax_; } + CPU::RegisterPair16 &axp() { return ax_; } - uint8_t &cl() { return cx_.halves.low; } - uint8_t &ch() { return cx_.halves.high; } - uint16_t &cx() { return cx_.full; } + uint8_t &cl() { return cx_.halves.low; } + uint8_t &ch() { return cx_.halves.high; } + uint16_t &cx() { return cx_.full; } - uint8_t &dl() { return dx_.halves.low; } - uint8_t &dh() { return dx_.halves.high; } - uint16_t &dx() { return dx_.full; } + uint8_t &dl() { return dx_.halves.low; } + uint8_t &dh() { return dx_.halves.high; } + uint16_t &dx() { return dx_.full; } - uint8_t &bl() { return bx_.halves.low; } - uint8_t &bh() { return bx_.halves.high; } - uint16_t &bx() { return bx_.full; } + uint8_t &bl() { return bx_.halves.low; } + uint8_t &bh() { return bx_.halves.high; } + uint16_t &bx() { return bx_.full; } - uint16_t &sp() { return sp_; } - uint16_t &bp() { return bp_; } - uint16_t &si() { return si_; } - uint16_t &di() { return di_; } + uint16_t &sp() { return sp_; } + uint16_t &bp() { return bp_; } + uint16_t &si() { return si_; } + uint16_t &di() { return di_; } - uint16_t &ip() { return ip_; } + uint16_t &ip() { return ip_; } - uint16_t &es() { return es_; } - uint16_t &cs() { return cs_; } - uint16_t &ds() { return ds_; } - uint16_t &ss() { return ss_; } + uint16_t &es() { return es_; } + uint16_t &cs() { return cs_; } + uint16_t &ds() { return ds_; } + uint16_t &ss() { return ss_; } - const uint16_t es() const { return es_; } - const uint16_t cs() const { return cs_; } - const uint16_t ds() const { return ds_; } - const uint16_t ss() const { return ss_; } + const uint16_t es() const { return es_; } + const uint16_t cs() const { return cs_; } + const uint16_t ds() const { return ds_; } + const uint16_t ss() const { return ss_; } - bool operator ==(const Registers &rhs) const { - return - ax_.full == rhs.ax_.full && - cx_.full == rhs.cx_.full && - dx_.full == rhs.dx_.full && - bx_.full == rhs.bx_.full && - sp_ == rhs.sp_ && - bp_ == rhs.bp_ && - si_ == rhs.si_ && - di_ == rhs.di_ && - es_ == rhs.es_ && - cs_ == rhs.cs_ && - ds_ == rhs.ds_ && - si_ == rhs.si_ && - ip_ == rhs.ip_; - } + bool operator ==(const Registers &rhs) const { + return + ax_.full == rhs.ax_.full && + cx_.full == rhs.cx_.full && + dx_.full == rhs.dx_.full && + bx_.full == rhs.bx_.full && + sp_ == rhs.sp_ && + bp_ == rhs.bp_ && + si_ == rhs.si_ && + di_ == rhs.di_ && + es_ == rhs.es_ && + cs_ == rhs.cs_ && + ds_ == rhs.ds_ && + si_ == rhs.si_ && + ip_ == rhs.ip_; + } - private: - CPU::RegisterPair16 ax_; - CPU::RegisterPair16 cx_; - CPU::RegisterPair16 dx_; - CPU::RegisterPair16 bx_; +private: + CPU::RegisterPair16 ax_; + CPU::RegisterPair16 cx_; + CPU::RegisterPair16 dx_; + CPU::RegisterPair16 bx_; - uint16_t sp_; - uint16_t bp_; - uint16_t si_; - uint16_t di_; - uint16_t es_, cs_, ds_, ss_; - uint16_t ip_; + uint16_t sp_; + uint16_t bp_; + uint16_t si_; + uint16_t di_; + uint16_t es_, cs_, ds_, ss_; + uint16_t ip_; }; class Segments { - public: - Segments(const Registers ®isters) : registers_(registers) {} +public: + Segments(const Registers ®isters) : registers_(registers) {} - using Source = InstructionSet::x86::Source; + using Source = InstructionSet::x86::Source; - /// Posted by @c perform after any operation which *might* have affected a segment register. - void did_update(Source segment) { - switch(segment) { - default: break; - case Source::ES: es_base_ = registers_.es() << 4; break; - case Source::CS: cs_base_ = registers_.cs() << 4; break; - case Source::DS: ds_base_ = registers_.ds() << 4; break; - case Source::SS: ss_base_ = registers_.ss() << 4; break; - } + /// Posted by @c perform after any operation which *might* have affected a segment register. + void did_update(Source segment) { + switch(segment) { + default: break; + case Source::ES: es_base_ = registers_.es() << 4; break; + case Source::CS: cs_base_ = registers_.cs() << 4; break; + case Source::DS: ds_base_ = registers_.ds() << 4; break; + case Source::SS: ss_base_ = registers_.ss() << 4; break; } + } - void reset() { - did_update(Source::ES); - did_update(Source::CS); - did_update(Source::DS); - did_update(Source::SS); - } + void reset() { + did_update(Source::ES); + did_update(Source::CS); + did_update(Source::DS); + did_update(Source::SS); + } - uint32_t es_base_, cs_base_, ds_base_, ss_base_; + uint32_t es_base_, cs_base_, ds_base_, ss_base_; - bool operator ==(const Segments &rhs) const { - return - es_base_ == rhs.es_base_ && - cs_base_ == rhs.cs_base_ && - ds_base_ == rhs.ds_base_ && - ss_base_ == rhs.ss_base_; - } + bool operator ==(const Segments &rhs) const { + return + es_base_ == rhs.es_base_ && + cs_base_ == rhs.cs_base_ && + ds_base_ == rhs.ds_base_ && + ss_base_ == rhs.ss_base_; + } - private: - const Registers ®isters_; +private: + const Registers ®isters_; }; struct Memory { - public: - using AccessType = InstructionSet::x86::AccessType; +public: + using AccessType = InstructionSet::x86::AccessType; - // Constructor. - Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) { - memory.resize(1024*1024); + // Constructor. + Memory(Registers ®isters, const Segments &segments) : registers_(registers), segments_(segments) { + memory.resize(1024*1024); + } + + // Initialisation. + void clear() { + tags.clear(); + } + + void seed(uint32_t address, uint8_t value) { + memory[address] = value; + tags[address] = Tag::Seeded; + } + + void touch(uint32_t address) { + tags[address] = Tag::AccessExpected; + } + + // + // Preauthorisation call-ins. + // + void preauthorise_stack_write(uint32_t length) { + uint16_t sp = registers_.sp(); + while(length--) { + --sp; + preauthorise(InstructionSet::x86::Source::SS, sp); } - - // Initialisation. - void clear() { - tags.clear(); + } + void preauthorise_stack_read(uint32_t length) { + uint16_t sp = registers_.sp(); + while(length--) { + preauthorise(InstructionSet::x86::Source::SS, sp); + ++sp; } - - void seed(uint32_t address, uint8_t value) { - memory[address] = value; - tags[address] = Tag::Seeded; + } + void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) { + while(length--) { + preauthorise(segment, start); + ++start; } - - void touch(uint32_t address) { - tags[address] = Tag::AccessExpected; + } + void preauthorise_read(uint32_t start, uint32_t length) { + while(length--) { + preauthorise(start); + ++start; } + } - // - // Preauthorisation call-ins. - // - void preauthorise_stack_write(uint32_t length) { - uint16_t sp = registers_.sp(); - while(length--) { - --sp; - preauthorise(InstructionSet::x86::Source::SS, sp); + // + // Access call-ins. + // + + // Accesses an address based on segment:offset. + template + typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset) { + return access(segment, offset, Tag::Accessed); + } + + // Accesses an address based on physical location. + template + typename InstructionSet::x86::Accessor::type access(uint32_t address) { + return access(address, Tag::Accessed); + } + + template + void write_back() { + if constexpr (std::is_same_v) { + if(write_back_address_[0] != NoWriteBack) { + memory[write_back_address_[0]] = write_back_value_ & 0xff; + memory[write_back_address_[1]] = write_back_value_ >> 8; + write_back_address_[0] = 0; } } - void preauthorise_stack_read(uint32_t length) { - uint16_t sp = registers_.sp(); - while(length--) { - preauthorise(InstructionSet::x86::Source::SS, sp); - ++sp; - } - } - void preauthorise_read(InstructionSet::x86::Source segment, uint16_t start, uint32_t length) { - while(length--) { - preauthorise(segment, start); - ++start; - } - } - void preauthorise_read(uint32_t start, uint32_t length) { - while(length--) { - preauthorise(start); - ++start; - } + } + + // + // Direct write. + // + template + void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) { + if(!test_preauthorisation(address(segment, offset))) { + printf("Non-preauthorised access\n"); } - // - // Access call-ins. - // - - // Accesses an address based on segment:offset. - template - typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset) { - return access(segment, offset, Tag::Accessed); + // Bytes can be written without further ado. + if constexpr (std::is_same_v) { + memory[address(segment, offset) & 0xf'ffff] = value; + return; } - // Accesses an address based on physical location. - template - typename InstructionSet::x86::Accessor::type access(uint32_t address) { - return access(address, Tag::Accessed); + // Words that straddle the segment end must be split in two. + if(offset == 0xffff) { + memory[address(segment, offset) & 0xf'ffff] = value & 0xff; + memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8; + return; } - template - void write_back() { - if constexpr (std::is_same_v) { - if(write_back_address_[0] != NoWriteBack) { - memory[write_back_address_[0]] = write_back_value_ & 0xff; - memory[write_back_address_[1]] = write_back_value_ >> 8; - write_back_address_[0] = 0; - } - } + const uint32_t target = address(segment, offset) & 0xf'ffff; + + // Words that straddle the end of physical RAM must also be split in two. + if(target == 0xf'ffff) { + memory[0xf'ffff] = value & 0xff; + memory[0x0'0000] = value >> 8; + return; } - // - // Direct write. - // - template - void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) { - if(!test_preauthorisation(address(segment, offset))) { - printf("Non-preauthorised access\n"); - } + // It's safe just to write then. + *reinterpret_cast(&memory[target]) = value; + } - // Bytes can be written without further ado. - if constexpr (std::is_same_v) { - memory[address(segment, offset) & 0xf'ffff] = value; - return; - } +private: + enum class Tag { + Seeded, + AccessExpected, + Accessed, + }; - // Words that straddle the segment end must be split in two. + std::unordered_set preauthorisations; + std::unordered_map tags; + std::vector memory; + Registers ®isters_; + const Segments &segments_; + + void preauthorise(uint32_t address) { + preauthorisations.insert(address); + } + void preauthorise(InstructionSet::x86::Source segment, uint16_t address) { + preauthorise((segment_base(segment) + address) & 0xf'ffff); + } + bool test_preauthorisation(uint32_t address) { + auto authorisation = preauthorisations.find(address); + if(authorisation == preauthorisations.end()) { + return false; + } + preauthorisations.erase(authorisation); + return true; + } + + uint32_t segment_base(InstructionSet::x86::Source segment) { + using Source = InstructionSet::x86::Source; + switch(segment) { + default: return segments_.ds_base_; + case Source::ES: return segments_.es_base_; + case Source::CS: return segments_.cs_base_; + case Source::SS: return segments_.ss_base_; + } + } + + uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) { + return (segment_base(segment) + offset) & 0xf'ffff; + } + + + // Entry point used by the flow controller so that it can mark up locations at which the flags were written, + // so that defined-flag-only masks can be applied while verifying RAM contents. + template + typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) { + const uint32_t physical_address = address(segment, offset); + + if constexpr (std::is_same_v) { + // If this is a 16-bit access that runs past the end of the segment, it'll wrap back + // to the start. So the 16-bit value will need to be a local cache. if(offset == 0xffff) { - memory[address(segment, offset) & 0xf'ffff] = value & 0xff; - memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8; - return; - } - - const uint32_t target = address(segment, offset) & 0xf'ffff; - - // Words that straddle the end of physical RAM must also be split in two. - if(target == 0xf'ffff) { - memory[0xf'ffff] = value & 0xff; - memory[0x0'0000] = value >> 8; - return; - } - - // It's safe just to write then. - *reinterpret_cast(&memory[target]) = value; - } - - private: - enum class Tag { - Seeded, - AccessExpected, - Accessed, - }; - - std::unordered_set preauthorisations; - std::unordered_map tags; - std::vector memory; - Registers ®isters_; - const Segments &segments_; - - void preauthorise(uint32_t address) { - preauthorisations.insert(address); - } - void preauthorise(InstructionSet::x86::Source segment, uint16_t address) { - preauthorise((segment_base(segment) + address) & 0xf'ffff); - } - bool test_preauthorisation(uint32_t address) { - auto authorisation = preauthorisations.find(address); - if(authorisation == preauthorisations.end()) { - return false; - } - preauthorisations.erase(authorisation); - return true; - } - - uint32_t segment_base(InstructionSet::x86::Source segment) { - using Source = InstructionSet::x86::Source; - switch(segment) { - default: return segments_.ds_base_; - case Source::ES: return segments_.es_base_; - case Source::CS: return segments_.cs_base_; - case Source::SS: return segments_.ss_base_; + return split_word(physical_address, address(segment, 0), tag); } } - uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) { - return (segment_base(segment) + offset) & 0xf'ffff; - } + return access(physical_address, tag); + } - - // Entry point used by the flow controller so that it can mark up locations at which the flags were written, - // so that defined-flag-only masks can be applied while verifying RAM contents. - template - typename InstructionSet::x86::Accessor::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) { - const uint32_t physical_address = address(segment, offset); - - if constexpr (std::is_same_v) { - // If this is a 16-bit access that runs past the end of the segment, it'll wrap back - // to the start. So the 16-bit value will need to be a local cache. - if(offset == 0xffff) { - return split_word(physical_address, address(segment, 0), tag); - } - } - - return access(physical_address, tag); - } - - // An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative - // to a segment, they're just at an absolute location. - template - typename InstructionSet::x86::Accessor::type access(uint32_t address, Tag tag) { - if constexpr (type == AccessType::PreauthorisedRead) { - if(!test_preauthorisation(address)) { - printf("Non preauthorised access\n"); - } - } - - for(size_t c = 0; c < sizeof(IntT); c++) { - tags[(address + c) & 0xf'ffff] = tag; - } - - // Dispense with the single-byte case trivially. - if constexpr (std::is_same_v) { - return memory[address]; - } else if(address != 0xf'ffff) { - return *reinterpret_cast(&memory[address]); - } else { - return split_word(address, 0, tag); + // An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative + // to a segment, they're just at an absolute location. + template + typename InstructionSet::x86::Accessor::type access(uint32_t address, Tag tag) { + if constexpr (type == AccessType::PreauthorisedRead) { + if(!test_preauthorisation(address)) { + printf("Non preauthorised access\n"); } } - template - typename InstructionSet::x86::Accessor::type - split_word(uint32_t low_address, uint32_t high_address, Tag tag) { - if constexpr (is_writeable(type)) { - write_back_address_[0] = low_address; - write_back_address_[1] = high_address; - tags[low_address] = tag; - tags[high_address] = tag; - - // Prepopulate only if this is a modify. - if constexpr (type == AccessType::ReadModifyWrite) { - write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8); - } - - return write_back_value_; - } else { - return memory[low_address] | (memory[high_address] << 8); - } + for(size_t c = 0; c < sizeof(IntT); c++) { + tags[(address + c) & 0xf'ffff] = tag; } - static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back. - uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack}; - uint16_t write_back_value_; + // Dispense with the single-byte case trivially. + if constexpr (std::is_same_v) { + return memory[address]; + } else if(address != 0xf'ffff) { + return *reinterpret_cast(&memory[address]); + } else { + return split_word(address, 0, tag); + } + } + + template + typename InstructionSet::x86::Accessor::type + split_word(uint32_t low_address, uint32_t high_address, Tag tag) { + if constexpr (is_writeable(type)) { + write_back_address_[0] = low_address; + write_back_address_[1] = high_address; + tags[low_address] = tag; + tags[high_address] = tag; + + // Prepopulate only if this is a modify. + if constexpr (type == AccessType::ReadModifyWrite) { + write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8); + } + + return write_back_value_; + } else { + return memory[low_address] | (memory[high_address] << 8); + } + } + + static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back. + uint32_t write_back_address_[2] = {NoWriteBack, NoWriteBack}; + uint16_t write_back_value_; }; struct IO { template void out([[maybe_unused]] uint16_t port, [[maybe_unused]] IntT value) {} template IntT in([[maybe_unused]] uint16_t port) { return IntT(~0); } }; class FlowController { - public: - FlowController(Registers ®isters, Segments &segments) : - registers_(registers), segments_(segments) {} +public: + FlowController(Registers ®isters, Segments &segments) : + registers_(registers), segments_(segments) {} - // Requirements for perform. - template - void jump(AddressT address) { - static_assert(std::is_same_v); - registers_.ip() = address; - } + // Requirements for perform. + template + void jump(AddressT address) { + static_assert(std::is_same_v); + registers_.ip() = address; + } - template - void jump(uint16_t segment, AddressT address) { - static_assert(std::is_same_v); - registers_.cs() = segment; - segments_.did_update(Segments::Source::CS); - registers_.ip() = address; - } + template + void jump(uint16_t segment, AddressT address) { + static_assert(std::is_same_v); + registers_.cs() = segment; + segments_.did_update(Segments::Source::CS); + registers_.ip() = address; + } - void halt() {} - void wait() {} + void halt() {} + void wait() {} - void repeat_last() { - should_repeat_ = true; - } + void repeat_last() { + should_repeat_ = true; + } - // Other actions. - void begin_instruction() { - should_repeat_ = false; - } - bool should_repeat() const { - return should_repeat_; - } + // Other actions. + void begin_instruction() { + should_repeat_ = false; + } + bool should_repeat() const { + return should_repeat_; + } - private: - Registers ®isters_; - Segments &segments_; - bool should_repeat_ = false; +private: + Registers ®isters_; + Segments &segments_; + bool should_repeat_ = false; }; struct ExecutionSupport { diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine.mm b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine.mm index 385e4270a..c165d5920 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine.mm @@ -17,15 +17,15 @@ #pragma mark - C++ delegate handlers class MachineTrapHandler: public CPU::AllRAMProcessor::TrapHandler { - public: - MachineTrapHandler(CSTestMachine *targetMachine) : target_(targetMachine) {} +public: + MachineTrapHandler(CSTestMachine *targetMachine) : target_(targetMachine) {} - void processor_did_trap(CPU::AllRAMProcessor &, uint16_t address) { - [target_ testMachineDidTrapAtAddress:address]; - } + void processor_did_trap(CPU::AllRAMProcessor &, uint16_t address) { + [target_ testMachineDidTrapAtAddress:address]; + } - private: - CSTestMachine *target_; +private: + CSTestMachine *target_; }; #pragma mark - The test machine diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm index 581e99d94..fb96a25e3 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm @@ -20,15 +20,15 @@ #pragma mark - C++ delegate handlers class BusOperationHandler: public CPU::Z80::AllRAMProcessor::MemoryAccessDelegate { - public: - BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {} +public: + BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {} - void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) { - [target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp]; - } + void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) { + [target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp]; + } - private: - CSTestMachineZ80 *target_; +private: + CSTestMachineZ80 *target_; }; #pragma mark - Register enum map diff --git a/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp b/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp index ae2258cc2..63f0f7e94 100644 --- a/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp +++ b/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp @@ -13,44 +13,44 @@ #include "68000.hpp" class ComparativeBusHandler: public CPU::MC68000::BusHandler { - public: - ComparativeBusHandler(const char *trace_name) { - trace = gzopen(trace_name, "rt"); +public: + ComparativeBusHandler(const char *trace_name) { + trace = gzopen(trace_name, "rt"); + } + + ~ComparativeBusHandler() { + gzclose(trace); + } + + void will_perform(uint32_t address, uint16_t) { + // Obtain the next line from the trace file. + char correct_state[300] = "\n"; + gzgets(trace, correct_state, sizeof(correct_state)); + ++line_count; + + // Generate state locally. + const auto state = get_state().registers; + char local_state[300]; + snprintf(local_state, sizeof(local_state), "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + address, + state.status, + state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7], + state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6], + state.stack_pointer() + ); + + // Check that the two coincide. + if(strcmp(correct_state, local_state)) { + fprintf(stderr, "Diverges at line %d\n", line_count); + fprintf(stderr, "Good: %s", correct_state); + fprintf(stderr, "Bad: %s", local_state); + throw std::exception(); } + } - ~ComparativeBusHandler() { - gzclose(trace); - } + virtual CPU::MC68000::State get_state() = 0; - void will_perform(uint32_t address, uint16_t) { - // Obtain the next line from the trace file. - char correct_state[300] = "\n"; - gzgets(trace, correct_state, sizeof(correct_state)); - ++line_count; - - // Generate state locally. - const auto state = get_state().registers; - char local_state[300]; - snprintf(local_state, sizeof(local_state), "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", - address, - state.status, - state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7], - state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6], - state.stack_pointer() - ); - - // Check that the two coincide. - if(strcmp(correct_state, local_state)) { - fprintf(stderr, "Diverges at line %d\n", line_count); - fprintf(stderr, "Good: %s", correct_state); - fprintf(stderr, "Bad: %s", local_state); - throw std::exception(); - } - } - - virtual CPU::MC68000::State get_state() = 0; - - private: - int line_count = 0; - gzFile trace; +private: + int line_count = 0; + gzFile trace; }; diff --git a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm index ae91b68b5..6a804e1c0 100644 --- a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm +++ b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm @@ -18,82 +18,82 @@ #include "CSROMFetcher.hpp" class EmuTOS: public ComparativeBusHandler { - public: - EmuTOS(const std::vector &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) { - assert(!(emuTOS.size() & 1)); - emuTOS_.resize(emuTOS.size() / 2); +public: + EmuTOS(const std::vector &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) { + assert(!(emuTOS.size() & 1)); + emuTOS_.resize(emuTOS.size() / 2); - for(size_t c = 0; c < emuTOS_.size(); ++c) { - emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1]; - } + for(size_t c = 0; c < emuTOS_.size(); ++c) { + emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1]; + } + } + + void run_for(HalfCycles cycles) { + m68000_.run_for(cycles); + } + + CPU::MC68000::State get_state() final { + return m68000_.get_state(); + } + + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + const uint32_t address = cycle.word_address(); + uint32_t word_address = address; + + // As much about the Atari ST's memory map as is relevant here: the ROM begins + // at 0xfc0000, and the first eight bytes are mirrored to the first four memory + // addresses in order for /RESET to work properly. RAM otherwise fills the first + // 512kb of the address space. Trying to write to ROM raises a bus error. + + const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4; + const bool is_ram = word_address < ram_.size(); + const bool is_peripheral = !is_rom && !is_ram; + + uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data(); + if(is_rom) { + word_address %= emuTOS_.size(); + } else { + word_address %= ram_.size(); } - void run_for(HalfCycles cycles) { - m68000_.run_for(cycles); - } - - CPU::MC68000::State get_state() final { - return m68000_.get_state(); - } - - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - const uint32_t address = cycle.word_address(); - uint32_t word_address = address; - - // As much about the Atari ST's memory map as is relevant here: the ROM begins - // at 0xfc0000, and the first eight bytes are mirrored to the first four memory - // addresses in order for /RESET to work properly. RAM otherwise fills the first - // 512kb of the address space. Trying to write to ROM raises a bus error. - - const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4; - const bool is_ram = word_address < ram_.size(); - const bool is_peripheral = !is_rom && !is_ram; - - uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data(); - if(is_rom) { - word_address %= emuTOS_.size(); - } else { - word_address %= ram_.size(); - } - - if(cycle.data_select_active()) { - uint16_t peripheral_result = 0xffff; - if(is_peripheral) { - switch(address & 0x7ff) { - // A hard-coded value for TIMER B. - case (0xa21 >> 1): - peripheral_result = 0x00000001; - break; - } - } - - using namespace CPU::MC68000; - switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) { - default: break; - - case Operation::SelectWord | Operation::Read: - cycle.value->w = is_peripheral ? peripheral_result : base[word_address]; - break; - case Operation::SelectByte | Operation::Read: - cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift(); - break; - case Operation::SelectWord: - base[word_address] = cycle.value->w; - break; - case Operation::SelectByte: - base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask())); + if(cycle.data_select_active()) { + uint16_t peripheral_result = 0xffff; + if(is_peripheral) { + switch(address & 0x7ff) { + // A hard-coded value for TIMER B. + case (0xa21 >> 1): + peripheral_result = 0x00000001; break; } } - return HalfCycles(0); + using namespace CPU::MC68000; + switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) { + default: break; + + case Operation::SelectWord | Operation::Read: + cycle.value->w = is_peripheral ? peripheral_result : base[word_address]; + break; + case Operation::SelectByte | Operation::Read: + cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift(); + break; + case Operation::SelectWord: + base[word_address] = cycle.value->w; + break; + case Operation::SelectByte: + base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask())); + break; + } } - private: - CPU::MC68000::Processor m68000_; + return HalfCycles(0); + } - std::vector emuTOS_; - std::array ram_; +private: + CPU::MC68000::Processor m68000_; + + std::vector emuTOS_; + std::array ram_; }; @interface EmuTOSTests : XCTestCase diff --git a/OSBindings/Mac/Clock SignalTests/QLTests.mm b/OSBindings/Mac/Clock SignalTests/QLTests.mm index 35d7a95d2..0147e9900 100644 --- a/OSBindings/Mac/Clock SignalTests/QLTests.mm +++ b/OSBindings/Mac/Clock SignalTests/QLTests.mm @@ -21,73 +21,73 @@ #include "CSROMFetcher.hpp" class QL: public ComparativeBusHandler { - public: - QL(const std::vector &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) { - assert(!(rom.size() & 1)); - rom_.resize(rom.size() / 2); +public: + QL(const std::vector &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) { + assert(!(rom.size() & 1)); + rom_.resize(rom.size() / 2); - for(size_t c = 0; c < rom_.size(); ++c) { - rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1]; + for(size_t c = 0; c < rom_.size(); ++c) { + rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1]; + } + } + + void run_for(HalfCycles cycles) { + m68000_.run_for(cycles); + } + + CPU::MC68000::State get_state() final { + return m68000_.get_state(); + } + + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + const uint32_t address = cycle.word_address(); + uint32_t word_address = address; + + // QL memory map: ROM is in the lowest area; RAM is from 0x20000. + const bool is_rom = word_address < rom_.size(); + const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size(); + const bool is_peripheral = !is_ram && !is_rom; + + uint16_t *const base = is_rom ? rom_.data() : ram_.data(); + if(is_rom) { + word_address %= rom_.size(); + } + if(is_ram) { + word_address %= ram_.size(); + } + + if(cycle.data_select_active()) { + uint16_t peripheral_result = 0xffff; + + using namespace CPU::MC68000; + switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) { + default: break; + + case Operation::SelectWord | Operation::Read: + cycle.value->w = is_peripheral ? peripheral_result : base[word_address]; + break; + case Operation::SelectByte | Operation::Read: + cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift(); + break; + case Operation::SelectWord: + assert(!(is_rom && !is_peripheral)); + if(!is_peripheral) base[word_address] = cycle.value->w; + break; + case Operation::SelectByte: + assert(!(is_rom && !is_peripheral)); + if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask())); + break; } } - void run_for(HalfCycles cycles) { - m68000_.run_for(cycles); - } + return HalfCycles(0); + } - CPU::MC68000::State get_state() final { - return m68000_.get_state(); - } +private: + CPU::MC68000::Processor m68000_; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - const uint32_t address = cycle.word_address(); - uint32_t word_address = address; - - // QL memory map: ROM is in the lowest area; RAM is from 0x20000. - const bool is_rom = word_address < rom_.size(); - const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size(); - const bool is_peripheral = !is_ram && !is_rom; - - uint16_t *const base = is_rom ? rom_.data() : ram_.data(); - if(is_rom) { - word_address %= rom_.size(); - } - if(is_ram) { - word_address %= ram_.size(); - } - - if(cycle.data_select_active()) { - uint16_t peripheral_result = 0xffff; - - using namespace CPU::MC68000; - switch(cycle.operation & (Operation::SelectWord | Operation::SelectByte | Operation::Read)) { - default: break; - - case Operation::SelectWord | Operation::Read: - cycle.value->w = is_peripheral ? peripheral_result : base[word_address]; - break; - case Operation::SelectByte | Operation::Read: - cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift(); - break; - case Operation::SelectWord: - assert(!(is_rom && !is_peripheral)); - if(!is_peripheral) base[word_address] = cycle.value->w; - break; - case Operation::SelectByte: - assert(!(is_rom && !is_peripheral)); - if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask())); - break; - } - } - - return HalfCycles(0); - } - - private: - CPU::MC68000::Processor m68000_; - - std::vector rom_; - std::array ram_; + std::vector rom_; + std::array ram_; }; @interface QLTests : XCTestCase diff --git a/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp b/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp index 4250e56b3..c860578c3 100644 --- a/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp +++ b/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp @@ -22,115 +22,115 @@ using namespace InstructionSet::M68k; begin execution at 0x0400. */ class RAM68000: public CPU::MC68000::BusHandler { - public: - RAM68000() : m68000_(*this) {} +public: + RAM68000() : m68000_(*this) {} - uint32_t initial_pc() const { - return 0x1000; + uint32_t initial_pc() const { + return 0x1000; + } + + void set_program( + const std::vector &program, + uint32_t stack_pointer = 0x206 + ) { + memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t)); + + // Ensure the condition codes start unset and set the initial program counter + // and supervisor stack pointer, as well as starting in supervisor mode. + auto registers = m68000_.get_state().registers; + registers.status = 0x2700; + registers.program_counter = initial_pc(); + registers.supervisor_stack_pointer = stack_pointer; + m68000_.decode_from_state(registers); + } + + void set_registers(std::function func) { + auto state = m68000_.get_state(); + func(state.registers); + m68000_.set_state(state); + } + + void will_perform(uint32_t, uint16_t) { + --instructions_remaining_; + if(instructions_remaining_ < 0) { + throw StopException(); } + } - void set_program( - const std::vector &program, - uint32_t stack_pointer = 0x206 - ) { - memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t)); + void run_for_instructions(int count) { + duration_ = HalfCycles(0); + instructions_remaining_ = count; + if(!instructions_remaining_) return; - // Ensure the condition codes start unset and set the initial program counter - // and supervisor stack pointer, as well as starting in supervisor mode. - auto registers = m68000_.get_state().registers; - registers.status = 0x2700; - registers.program_counter = initial_pc(); - registers.supervisor_stack_pointer = stack_pointer; - m68000_.decode_from_state(registers); - } - - void set_registers(std::function func) { - auto state = m68000_.get_state(); - func(state.registers); - m68000_.set_state(state); - } - - void will_perform(uint32_t, uint16_t) { - --instructions_remaining_; - if(instructions_remaining_ < 0) { - throw StopException(); + try { + while(true) { + run_for(HalfCycles(2000)); } - } + } catch (const StopException &) {} + } - void run_for_instructions(int count) { - duration_ = HalfCycles(0); - instructions_remaining_ = count; - if(!instructions_remaining_) return; + void run_for(HalfCycles cycles) { + m68000_.run_for(cycles); + } - try { - while(true) { - run_for(HalfCycles(2000)); - } - } catch (const StopException &) {} - } + uint16_t *ram_at(uint32_t address) { + return &ram_[(address >> 1) % ram_.size()]; + } - void run_for(HalfCycles cycles) { - m68000_.run_for(cycles); - } + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + const uint32_t word_address = cycle.word_address(); + duration_ += cycle.length; - uint16_t *ram_at(uint32_t address) { - return &ram_[(address >> 1) % ram_.size()]; - } + if(cycle.data_select_active()) { + if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) { + cycle.value->b = 10; + } else { + switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { + default: break; - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - const uint32_t word_address = cycle.word_address(); - duration_ += cycle.length; - - if(cycle.data_select_active()) { - if(cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) { - cycle.value->b = 10; - } else { - switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { - default: break; - - case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: - cycle.value->w = ram_[word_address % ram_.size()]; - break; - case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: - cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift(); - break; - case CPU::MC68000::Operation::SelectWord: - ram_[word_address % ram_.size()] = cycle.value->w; - break; - case CPU::MC68000::Operation::SelectByte: - ram_[word_address % ram_.size()] = uint16_t( - (cycle.value->b << cycle.byte_shift()) | - (ram_[word_address % ram_.size()] & cycle.untouched_byte_mask()) - ); - break; - } + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: + cycle.value->w = ram_[word_address % ram_.size()]; + break; + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: + cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift(); + break; + case CPU::MC68000::Operation::SelectWord: + ram_[word_address % ram_.size()] = cycle.value->w; + break; + case CPU::MC68000::Operation::SelectByte: + ram_[word_address % ram_.size()] = uint16_t( + (cycle.value->b << cycle.byte_shift()) | + (ram_[word_address % ram_.size()] & cycle.untouched_byte_mask()) + ); + break; } } - - return HalfCycles(0); } - CPU::MC68000::State get_processor_state() { - return m68000_.get_state(); - } + return HalfCycles(0); + } - auto &processor() { - return m68000_; - } + CPU::MC68000::State get_processor_state() { + return m68000_.get_state(); + } - int get_cycle_count() { - return int(duration_.as_integral()) >> 1; - } + auto &processor() { + return m68000_; + } - void reset_cycle_count() { - duration_ = HalfCycles(0); - } + int get_cycle_count() { + return int(duration_.as_integral()) >> 1; + } - private: - struct StopException {}; + void reset_cycle_count() { + duration_ = HalfCycles(0); + } - CPU::MC68000::Processor m68000_; - std::array ram_{}; - int instructions_remaining_; - HalfCycles duration_; +private: + struct StopException {}; + + CPU::MC68000::Processor m68000_; + std::array ram_{}; + int instructions_remaining_; + HalfCycles duration_; }; diff --git a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm index 5404a15de..0ce20c6f9 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm +++ b/OSBindings/Mac/Clock SignalTests/Z80ContentionTests.mm @@ -179,13 +179,13 @@ struct CapturingZ80: public CPU::Z80::BusHandler { return contentions_48k_; } - private: - CPU::Z80::Processor z80_; - uint8_t ram_[65536]; - uint16_t code_length_ = 0; +private: + CPU::Z80::Processor z80_; + uint8_t ram_[65536]; + uint16_t code_length_ = 0; - std::vector bus_records_; - std::vector contentions_48k_; + std::vector bus_records_; + std::vector contentions_48k_; }; } diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 2231268c8..07e40bec5 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -93,85 +93,85 @@ struct MachineRunner { std::mutex *machine_mutex; Machine::DynamicMachine *machine; - private: - SDL_TimerID timer_ = 0; - Time::Nanos last_time_ = 0; - std::atomic vsync_time_; - std::atomic_flag frame_lock_; +private: + SDL_TimerID timer_ = 0; + Time::Nanos last_time_ = 0; + std::atomic vsync_time_; + std::atomic_flag frame_lock_; - enum class State { - Running, - Stopping, - Stopped - }; - std::atomic state_{State::Running}; + enum class State { + Running, + Stopping, + Stopped + }; + std::atomic state_{State::Running}; - Time::ScanSynchroniser scan_synchroniser_; + Time::ScanSynchroniser scan_synchroniser_; - // A slightly clumsy means of trying to derive frame rate from calls to - // signal_vsync(); SDL_DisplayMode provides only an integral quantity - // whereas, empirically, it's fairly common for monitors to run at the - // NTSC-esque frame rates of 59.94Hz. - std::array frame_times_; - Time::Nanos frame_time_average_ = 0; - size_t frame_time_pointer_ = 0; - std::atomic _frame_period; + // A slightly clumsy means of trying to derive frame rate from calls to + // signal_vsync(); SDL_DisplayMode provides only an integral quantity + // whereas, empirically, it's fairly common for monitors to run at the + // NTSC-esque frame rates of 59.94Hz. + std::array frame_times_; + Time::Nanos frame_time_average_ = 0; + size_t frame_time_pointer_ = 0; + std::atomic _frame_period; - static constexpr Uint32 timer_period = 4; - static Uint32 sdl_callback(Uint32, void *param) { - reinterpret_cast(param)->update(); - return timer_period; + static constexpr Uint32 timer_period = 4; + static Uint32 sdl_callback(Uint32, void *param) { + reinterpret_cast(param)->update(); + return timer_period; + } + + void update() { + // If a shutdown is in progress, signal stoppage and do nothing. + if(state_ != State::Running) { + state_ = State::Stopped; + return; } - void update() { - // If a shutdown is in progress, signal stoppage and do nothing. - if(state_ != State::Running) { - state_ = State::Stopped; - return; - } - - // Get time now and determine how long it has been since the last time this - // function was called. If it's more than half a second then forego any activity - // now, as there's obviously been some sort of substantial time glitch. - const auto time_now = Time::nanos_now(); - if(time_now - last_time_ > Time::Nanos(500'000'000)) { - last_time_ = time_now - Time::Nanos(500'000'000); - } - - const auto vsync_time = vsync_time_.load(); - - std::unique_lock lock_guard(*machine_mutex); - const auto scan_producer = machine->scan_producer(); - const auto timed_machine = machine->timed_machine(); - - bool split_and_sync = false; - if(last_time_ < vsync_time && time_now >= vsync_time) { - split_and_sync = scan_synchroniser_.can_synchronise(scan_producer->get_scan_status(), _frame_period); - } - - if(split_and_sync) { - timed_machine->run_for(double(vsync_time - last_time_) / 1e9); - timed_machine->flush_output(MachineTypes::TimedMachine::Output::All); - timed_machine->set_speed_multiplier( - scan_synchroniser_.next_speed_multiplier(scan_producer->get_scan_status()) - ); - - // This is a bit of an SDL ugliness; wait here until the next frame is drawn. - // That is, unless and until I can think of a good way of running background - // updates via a share group — possibly an extra intermediate buffer is needed? - lock_guard.unlock(); - while(frame_lock_.test_and_set()); - lock_guard.lock(); - - timed_machine->run_for(double(time_now - vsync_time) / 1e9); - timed_machine->flush_output(MachineTypes::TimedMachine::Output::All); - } else { - timed_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier()); - timed_machine->run_for(double(time_now - last_time_) / 1e9); - timed_machine->flush_output(MachineTypes::TimedMachine::Output::All); - } - last_time_ = time_now; + // Get time now and determine how long it has been since the last time this + // function was called. If it's more than half a second then forego any activity + // now, as there's obviously been some sort of substantial time glitch. + const auto time_now = Time::nanos_now(); + if(time_now - last_time_ > Time::Nanos(500'000'000)) { + last_time_ = time_now - Time::Nanos(500'000'000); } + + const auto vsync_time = vsync_time_.load(); + + std::unique_lock lock_guard(*machine_mutex); + const auto scan_producer = machine->scan_producer(); + const auto timed_machine = machine->timed_machine(); + + bool split_and_sync = false; + if(last_time_ < vsync_time && time_now >= vsync_time) { + split_and_sync = scan_synchroniser_.can_synchronise(scan_producer->get_scan_status(), _frame_period); + } + + if(split_and_sync) { + timed_machine->run_for(double(vsync_time - last_time_) / 1e9); + timed_machine->flush_output(MachineTypes::TimedMachine::Output::All); + timed_machine->set_speed_multiplier( + scan_synchroniser_.next_speed_multiplier(scan_producer->get_scan_status()) + ); + + // This is a bit of an SDL ugliness; wait here until the next frame is drawn. + // That is, unless and until I can think of a good way of running background + // updates via a share group — possibly an extra intermediate buffer is needed? + lock_guard.unlock(); + while(frame_lock_.test_and_set()); + lock_guard.lock(); + + timed_machine->run_for(double(time_now - vsync_time) / 1e9); + timed_machine->flush_output(MachineTypes::TimedMachine::Output::All); + } else { + timed_machine->set_speed_multiplier(scan_synchroniser_.get_base_speed_multiplier()); + timed_machine->run_for(double(time_now - last_time_) / 1e9); + timed_machine->flush_output(MachineTypes::TimedMachine::Output::All); + } + last_time_ = time_now; + } }; struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { @@ -214,92 +214,92 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { }; class ActivityObserver: public Activity::Observer { - public: - ActivityObserver(Activity::Source *source, float aspect_ratio) { - // Get the suorce to supply all LEDs and drives. - source->set_activity_observer(this); +public: + ActivityObserver(Activity::Source *source, float aspect_ratio) { + // Get the suorce to supply all LEDs and drives. + source->set_activity_observer(this); - // The objective is to display drives on one side of the screen, other LEDs on the other. Drives - // may or may not have LEDs and this code intends to display only those which do; so a quick - // comparative processing of the two lists is called for. + // The objective is to display drives on one side of the screen, other LEDs on the other. Drives + // may or may not have LEDs and this code intends to display only those which do; so a quick + // comparative processing of the two lists is called for. - // Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed. - drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) { - return std::find(leds_.begin(), leds_.end(), string) == leds_.end(); - }) - drives_.begin()); + // Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed. + drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) { + return std::find(leds_.begin(), leds_.end(), string) == leds_.end(); + }) - drives_.begin()); - // Remove from the list of LEDs any which are drives. Those will be represented separately. - leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) { - return std::find(drives_.begin(), drives_.end(), string) != drives_.end(); - }) - leds_.begin()); + // Remove from the list of LEDs any which are drives. Those will be represented separately. + leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) { + return std::find(drives_.begin(), drives_.end(), string) != drives_.end(); + }) - leds_.begin()); - set_aspect_ratio(aspect_ratio); + set_aspect_ratio(aspect_ratio); + } + + void set_aspect_ratio(float aspect_ratio) { + std::lock_guard lock_guard(mutex); + lights_.clear(); + + // Generate a bunch of LEDs for connected drives. + constexpr float height = 0.05f; + const float width = height / aspect_ratio; + const float right_x = 1.0f - 2.0f * width; + float y = 1.0f - 2.0f * height; + for(const auto &drive: drives_) { + lights_.emplace(drive, std::make_unique(right_x, y, width, height)); + y -= height * 2.0f; } - void set_aspect_ratio(float aspect_ratio) { - std::lock_guard lock_guard(mutex); - lights_.clear(); + /* + This would generate LEDs for things other than drives; I'm declining for now + due to the inexpressiveness of just painting a rectangle. - // Generate a bunch of LEDs for connected drives. - constexpr float height = 0.05f; - const float width = height / aspect_ratio; - const float right_x = 1.0f - 2.0f * width; - float y = 1.0f - 2.0f * height; - for(const auto &drive: drives_) { - lights_.emplace(drive, std::make_unique(right_x, y, width, height)); + const float left_x = -1.0f + 2.0f * width; + y = 1.0f - 2.0f * height; + for(const auto &led: leds_) { + lights_.emplace(led, std::make_unique(left_x, y, width, height)); y -= height * 2.0f; } + */ + } - /* - This would generate LEDs for things other than drives; I'm declining for now - due to the inexpressiveness of just painting a rectangle. - - const float left_x = -1.0f + 2.0f * width; - y = 1.0f - 2.0f * height; - for(const auto &led: leds_) { - lights_.emplace(led, std::make_unique(left_x, y, width, height)); - y -= height * 2.0f; - } - */ + void draw() { + std::lock_guard lock_guard(mutex); + for(const auto &lit_led: lit_leds_) { + if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end()) + lights_[lit_led]->draw(0.0, 0.8, 0.0); } + blinking_leds_.clear(); + } - void draw() { - std::lock_guard lock_guard(mutex); - for(const auto &lit_led: lit_leds_) { - if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end()) - lights_[lit_led]->draw(0.0, 0.8, 0.0); - } - blinking_leds_.clear(); - } +private: + std::vector leds_; + void register_led(const std::string &name, uint8_t) final { + std::lock_guard lock_guard(mutex); + leds_.push_back(name); + } - private: - std::vector leds_; - void register_led(const std::string &name, uint8_t) final { - std::lock_guard lock_guard(mutex); - leds_.push_back(name); - } + std::vector drives_; + void register_drive(const std::string &name) final { + std::lock_guard lock_guard(mutex); + drives_.push_back(name); + } - std::vector drives_; - void register_drive(const std::string &name) final { - std::lock_guard lock_guard(mutex); - drives_.push_back(name); - } + void set_led_status(const std::string &name, bool lit) final { + std::lock_guard lock_guard(mutex); + if(lit) lit_leds_.insert(name); + else lit_leds_.erase(name); + } - void set_led_status(const std::string &name, bool lit) final { - std::lock_guard lock_guard(mutex); - if(lit) lit_leds_.insert(name); - else lit_leds_.erase(name); - } + void announce_drive_event(const std::string &name, DriveEvent) final { + std::lock_guard lock_guard(mutex); + blinking_leds_.insert(name); + } - void announce_drive_event(const std::string &name, DriveEvent) final { - std::lock_guard lock_guard(mutex); - blinking_leds_.insert(name); - } - - std::map> lights_; - std::set lit_leds_; - std::set blinking_leds_; - std::mutex mutex; + std::map> lights_; + std::set lit_leds_; + std::set blinking_leds_; + std::mutex mutex; }; bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key) { @@ -465,31 +465,31 @@ std::string system_get(const char *command) { Maintains a communicative window title. */ class DynamicWindowTitler { - public: - DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {} +public: + DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {} - std::string window_title() { - if(!mouse_is_captured_) return file_name_; - return file_name_ + " (press control+escape to release mouse)"; - } + std::string window_title() { + if(!mouse_is_captured_) return file_name_; + return file_name_ + " (press control+escape to release mouse)"; + } - void set_mouse_is_captured(bool is_captured) { - mouse_is_captured_ = is_captured; - update_window_title(); - } + void set_mouse_is_captured(bool is_captured) { + mouse_is_captured_ = is_captured; + update_window_title(); + } - void set_file_name(const std::string &name) { - file_name_ = name; - update_window_title(); - } + void set_file_name(const std::string &name) { + file_name_ = name; + update_window_title(); + } - private: - void update_window_title() { - SDL_SetWindowTitle(window_, window_title().c_str()); - } - bool mouse_is_captured_ = false; - SDL_Window *window_ = nullptr; - std::string file_name_; +private: + void update_window_title() { + SDL_SetWindowTitle(window_, window_title().c_str()); + } + bool mouse_is_captured_ = false; + SDL_Window *window_ = nullptr; + std::string file_name_; }; /*! @@ -497,37 +497,37 @@ class DynamicWindowTitler { of historic hat values. */ class SDLJoystick { - public: - SDLJoystick(SDL_Joystick *joystick) : joystick_(joystick) { - hat_values_.resize(SDL_JoystickNumHats(joystick)); - } +public: + SDLJoystick(SDL_Joystick *joystick) : joystick_(joystick) { + hat_values_.resize(SDL_JoystickNumHats(joystick)); + } - ~SDLJoystick() { - SDL_JoystickClose(joystick_); - } + ~SDLJoystick() { + SDL_JoystickClose(joystick_); + } - /// @returns The underlying SDL_Joystick. - SDL_Joystick *get() { - return joystick_; - } + /// @returns The underlying SDL_Joystick. + SDL_Joystick *get() { + return joystick_; + } - /// @returns A reference to the storage for the previous state of hat @c c. - Uint8 &last_hat_value(int c) { - return hat_values_[c]; - } + /// @returns A reference to the storage for the previous state of hat @c c. + Uint8 &last_hat_value(int c) { + return hat_values_[c]; + } - /// @returns The logic OR of all stored hat states. - Uint8 hat_values() { - Uint8 value = 0; - for(const auto hat_value: hat_values_) { - value |= hat_value; - } - return value; + /// @returns The logic OR of all stored hat states. + Uint8 hat_values() { + Uint8 value = 0; + for(const auto hat_value: hat_values_) { + value |= hat_value; } + return value; + } - private: - SDL_Joystick *joystick_; - std::vector hat_values_; +private: + SDL_Joystick *joystick_; + std::vector hat_values_; }; } diff --git a/Processors/Z80/AllRAM/Z80AllRAM.cpp b/Processors/Z80/AllRAM/Z80AllRAM.cpp index 35dea0c00..ddf3c6000 100644 --- a/Processors/Z80/AllRAM/Z80AllRAM.cpp +++ b/Processors/Z80/AllRAM/Z80AllRAM.cpp @@ -13,106 +13,106 @@ using namespace CPU::Z80; namespace { class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler { - public: - ConcreteAllRAMProcessor() : AllRAMProcessor(), z80_(*this) {} - - inline HalfCycles perform_machine_cycle(const PartialMachineCycle &cycle) { - timestamp_ += cycle.length; - if(!cycle.is_terminal()) { - return HalfCycles(0); - } - - uint16_t address = cycle.address ? *cycle.address : 0x0000; - switch(cycle.operation) { - case PartialMachineCycle::ReadOpcode: - check_address_for_trap(address); - case PartialMachineCycle::Read: - *cycle.value = memory_[address]; - break; - case PartialMachineCycle::Write: - memory_[address] = *cycle.value; - break; - - case PartialMachineCycle::Output: - break; - case PartialMachineCycle::Input: - *cycle.value = port_delegate_ ? port_delegate_->z80_all_ram_processor_input(address) : 0xff; - break; - - case PartialMachineCycle::Internal: - case PartialMachineCycle::Refresh: - break; - - case PartialMachineCycle::Interrupt: - // A pick that means LD HL, (nn) if interpreted as an instruction but is otherwise - // arbitrary. - *cycle.value = 0x21; - break; - - default: - break; - } - - if(memory_delegate_ != nullptr) { - memory_delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_); - } +public: + ConcreteAllRAMProcessor() : AllRAMProcessor(), z80_(*this) {} + inline HalfCycles perform_machine_cycle(const PartialMachineCycle &cycle) { + timestamp_ += cycle.length; + if(!cycle.is_terminal()) { return HalfCycles(0); } - void run_for(const Cycles cycles) final { - z80_.run_for(cycles); + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case PartialMachineCycle::ReadOpcode: + check_address_for_trap(address); + case PartialMachineCycle::Read: + *cycle.value = memory_[address]; + break; + case PartialMachineCycle::Write: + memory_[address] = *cycle.value; + break; + + case PartialMachineCycle::Output: + break; + case PartialMachineCycle::Input: + *cycle.value = port_delegate_ ? port_delegate_->z80_all_ram_processor_input(address) : 0xff; + break; + + case PartialMachineCycle::Internal: + case PartialMachineCycle::Refresh: + break; + + case PartialMachineCycle::Interrupt: + // A pick that means LD HL, (nn) if interpreted as an instruction but is otherwise + // arbitrary. + *cycle.value = 0x21; + break; + + default: + break; } - void run_for_instruction() final { - int toggles = 0; - int cycles = 0; + if(memory_delegate_ != nullptr) { + memory_delegate_->z80_all_ram_processor_did_perform_bus_operation(*this, cycle.operation, address, cycle.value ? *cycle.value : 0x00, timestamp_); + } - // Run: - // (1) until is_starting_new_instruction is true; - // (2) until it is false again; and - // (3) until it is true again. - while(true) { - if(z80_.is_starting_new_instruction() != (toggles&1)) { - ++toggles; - if(toggles == 3) break; - } - z80_.run_for(Cycles(1)); - ++cycles; + return HalfCycles(0); + } + + void run_for(const Cycles cycles) final { + z80_.run_for(cycles); + } + + void run_for_instruction() final { + int toggles = 0; + int cycles = 0; + + // Run: + // (1) until is_starting_new_instruction is true; + // (2) until it is false again; and + // (3) until it is true again. + while(true) { + if(z80_.is_starting_new_instruction() != (toggles&1)) { + ++toggles; + if(toggles == 3) break; } + z80_.run_for(Cycles(1)); + ++cycles; } + } - uint16_t value_of(Register r) final { - return z80_.value_of(r); - } + uint16_t value_of(Register r) final { + return z80_.value_of(r); + } - void set_value_of(Register r, uint16_t value) final { - z80_.set_value_of(r, value); - } + void set_value_of(Register r, uint16_t value) final { + z80_.set_value_of(r, value); + } - bool get_halt_line() final { - return z80_.get_halt_line(); - } + bool get_halt_line() final { + return z80_.get_halt_line(); + } - void reset_power_on() final { - return z80_.reset_power_on(); - } + void reset_power_on() final { + return z80_.reset_power_on(); + } - void set_interrupt_line(bool value) final { - z80_.set_interrupt_line(value); - } + void set_interrupt_line(bool value) final { + z80_.set_interrupt_line(value); + } - void set_non_maskable_interrupt_line(bool value) final { - z80_.set_non_maskable_interrupt_line(value); - } + void set_non_maskable_interrupt_line(bool value) final { + z80_.set_non_maskable_interrupt_line(value); + } - void set_wait_line(bool value) final { - z80_.set_wait_line(value); - } + void set_wait_line(bool value) final { + z80_.set_wait_line(value); + } - private: - CPU::Z80::Processor z80_; - bool was_m1_ = false; +private: + CPU::Z80::Processor z80_; + bool was_m1_ = false; }; } diff --git a/Reflection/Dispatcher.hpp b/Reflection/Dispatcher.hpp index 8830acef8..0ebf256c9 100644 --- a/Reflection/Dispatcher.hpp +++ b/Reflection/Dispatcher.hpp @@ -80,8 +80,8 @@ struct RangeDispatcher { } } - private: - template static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { +private: + template static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { #define index(n) \ case n: \ if constexpr (n <= SequencerT::max) { \ @@ -140,18 +140,18 @@ struct SubrangeDispatcher { #undef index } - private: - static constexpr int find_begin(int n) { - const auto type = ClassifierT::region(n); - while(n && ClassifierT::region(n - 1) == type) --n; - return n; - } +private: + static constexpr int find_begin(int n) { + const auto type = ClassifierT::region(n); + while(n && ClassifierT::region(n - 1) == type) --n; + return n; + } - static constexpr int find_end(int n) { - const auto type = ClassifierT::region(n); - while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; - return n; - } + static constexpr int find_end(int n) { + const auto type = ClassifierT::region(n); + while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; + return n; + } }; #undef switch_indices diff --git a/Reflection/Enum.hpp b/Reflection/Enum.hpp index c2233567a..1d8b705e7 100644 --- a/Reflection/Enum.hpp +++ b/Reflection/Enum.hpp @@ -39,132 +39,132 @@ namespace Reflection { No guarantees of speed or any other kind of efficiency are offered. */ class Enum { - public: - /*! - Registers @c name and the entries within @c declaration for the enum type @c Type. +public: + /*! + Registers @c name and the entries within @c declaration for the enum type @c Type. - Assuming the caller used the macros above, a standard pattern where both things can be placed in - the same namespace might look like: + Assuming the caller used the macros above, a standard pattern where both things can be placed in + the same namespace might look like: - ReflectableEnum(MyEnum, int, A, B, C); + ReflectableEnum(MyEnum, int, A, B, C); - ... + ... - AnnounceEnum(MyEnum) + AnnounceEnum(MyEnum) - If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the - EnumDeclaration macro. - */ - template static void declare(const char *name, const char *declaration) { - const char *d_ptr = declaration; + If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the + EnumDeclaration macro. + */ + template static void declare(const char *name, const char *declaration) { + const char *d_ptr = declaration; - std::vector result; - while(true) { - // Skip non-alphas, and exit if the terminator is found. - while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr; - if(!*d_ptr) break; + std::vector result; + while(true) { + // Skip non-alphas, and exit if the terminator is found. + while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr; + if(!*d_ptr) break; - // Note the current location and proceed for all alphas and digits. - const auto start = d_ptr; - while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr; + // Note the current location and proceed for all alphas and digits. + const auto start = d_ptr; + while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr; - // Add a string view. - result.emplace_back(std::string(start, size_t(d_ptr - start))); - } - - members_by_type_.emplace(std::type_index(typeid(Type)), result); - names_by_type_.emplace(std::type_index(typeid(Type)), std::string(name)); + // Add a string view. + result.emplace_back(std::string(start, size_t(d_ptr - start))); } - /*! - @returns the declared name of the enum @c Type if it has been registered; the empty string otherwise. - */ - template static const std::string &name() { - return name(typeid(Type)); - } + members_by_type_.emplace(std::type_index(typeid(Type)), result); + names_by_type_.emplace(std::type_index(typeid(Type)), std::string(name)); + } - /*! - @returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise. - */ - static const std::string &name(std::type_index type) { - const auto entry = names_by_type_.find(type); - if(entry == names_by_type_.end()) return empty_string_; - return entry->second; - } + /*! + @returns the declared name of the enum @c Type if it has been registered; the empty string otherwise. + */ + template static const std::string &name() { + return name(typeid(Type)); + } - /*! - @returns the number of members of the enum @c Type if it has been registered; 0 otherwise. - */ - template static size_t size() { - return size(typeid(Type)); - } + /*! + @returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise. + */ + static const std::string &name(std::type_index type) { + const auto entry = names_by_type_.find(type); + if(entry == names_by_type_.end()) return empty_string_; + return entry->second; + } - /*! - @returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise. - */ - static size_t size(std::type_index type) { - const auto entry = members_by_type_.find(type); - if(entry == members_by_type_.end()) return std::string::npos; - return entry->second.size(); - } + /*! + @returns the number of members of the enum @c Type if it has been registered; 0 otherwise. + */ + template static size_t size() { + return size(typeid(Type)); + } - /*! - @returns A @c std::string name for the enum value @c e. - */ - template static const std::string &to_string(Type e) { - return to_string(typeid(Type), int(e)); - } + /*! + @returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise. + */ + static size_t size(std::type_index type) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return std::string::npos; + return entry->second.size(); + } - /*! - @returns A @c std::string name for the enum value @c e from the enum with type_info @c type. - */ - static const std::string &to_string(std::type_index type, int e) { - const auto entry = members_by_type_.find(type); - if(entry == members_by_type_.end()) return empty_string_; - return entry->second[size_t(e)]; - } + /*! + @returns A @c std::string name for the enum value @c e. + */ + template static const std::string &to_string(Type e) { + return to_string(typeid(Type), int(e)); + } - /*! - @returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise. - */ - static const std::vector &all_values(std::type_index type) { - const auto entry = members_by_type_.find(type); - if(entry == members_by_type_.end()) return empty_vector_; - return entry->second; - } + /*! + @returns A @c std::string name for the enum value @c e from the enum with type_info @c type. + */ + static const std::string &to_string(std::type_index type, int e) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return empty_string_; + return entry->second[size_t(e)]; + } - /*! - @returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise. - */ - template static const std::vector &all_values() { - return all_values(typeid(Type)); - } + /*! + @returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise. + */ + static const std::vector &all_values(std::type_index type) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return empty_vector_; + return entry->second; + } - /*! - @returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if - the name is not found. - */ - template static Type from_string(const std::string &str) { - return Type(from_string(typeid(Type), str)); - } + /*! + @returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise. + */ + template static const std::vector &all_values() { + return all_values(typeid(Type)); + } - /*! - @returns A value for the name @c str in the enum with type_info @c type , or @c -1 if - the name is not found. - */ - static int from_string(std::type_index type, const std::string &str) { - const auto entry = members_by_type_.find(type); - if(entry == members_by_type_.end()) return -1; - const auto iterator = std::find(entry->second.begin(), entry->second.end(), str); - if(iterator == entry->second.end()) return -1; - return int(iterator - entry->second.begin()); - } + /*! + @returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if + the name is not found. + */ + template static Type from_string(const std::string &str) { + return Type(from_string(typeid(Type), str)); + } - private: - static inline std::unordered_map> members_by_type_; - static inline std::unordered_map names_by_type_; - static inline const std::string empty_string_; - static inline const std::vector empty_vector_; + /*! + @returns A value for the name @c str in the enum with type_info @c type , or @c -1 if + the name is not found. + */ + static int from_string(std::type_index type, const std::string &str) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return -1; + const auto iterator = std::find(entry->second.begin(), entry->second.end(), str); + if(iterator == entry->second.end()) return -1; + return int(iterator - entry->second.begin()); + } + +private: + static inline std::unordered_map> members_by_type_; + static inline std::unordered_map names_by_type_; + static inline const std::string empty_string_; + static inline const std::vector empty_vector_; }; } diff --git a/Reflection/Struct.cpp b/Reflection/Struct.cpp index 81c62fb2e..5067f9aca 100644 --- a/Reflection/Struct.cpp +++ b/Reflection/Struct.cpp @@ -435,11 +435,11 @@ struct ArrayReceiver: public Reflection::Struct { return nullptr; } - private: - Reflection::Struct *target_; - const std::type_info *type_; - std::string key_; - size_t count_; +private: + Reflection::Struct *target_; + const std::type_info *type_; + std::string key_; + size_t count_; }; } diff --git a/Reflection/Struct.hpp b/Reflection/Struct.hpp index dc6223bf1..2ba8b47cc 100644 --- a/Reflection/Struct.hpp +++ b/Reflection/Struct.hpp @@ -75,9 +75,9 @@ struct Struct { */ virtual bool should_serialise([[maybe_unused]] const std::string &key) const { return true; } - private: - void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const; - bool deserialise(const uint8_t *bson, size_t size); +private: + void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const; + bool deserialise(const uint8_t *bson, size_t size); }; /*! @@ -149,209 +149,209 @@ template bool get(const Struct &target, const std::string &name, template Type get(const Struct &target, const std::string &name, size_t offset = 0); template class StructImpl: public Struct { - public: - /*! - @returns the value of type @c Type that is loaded from the offset registered for the field @c name. - It is the caller's responsibility to provide an appropriate type of data. - */ - void *get(const std::string &name) final { - const auto iterator = contents_.find(name); - if(iterator == contents_.end()) return nullptr; - return reinterpret_cast(this) + iterator->second.offset; - } - - /*! - Stores the @c value of type @c Type to the offset registered for the field @c name. - +public: + /*! + @returns the value of type @c Type that is loaded from the offset registered for the field @c name. It is the caller's responsibility to provide an appropriate type of data. - */ - void set(const std::string &name, const void *value, size_t offset) final { - const auto iterator = contents_.find(name); - if(iterator == contents_.end()) return; - assert(offset < iterator->second.count); - memcpy(reinterpret_cast(this) + iterator->second.offset + offset * iterator->second.size, value, iterator->second.size); - } + */ + void *get(const std::string &name) final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return nullptr; + return reinterpret_cast(this) + iterator->second.offset; + } - /*! - @returns @c type_info for the field @c name. - */ - const std::type_info *type_of(const std::string &name) const final { - const auto iterator = contents_.find(name); - if(iterator == contents_.end()) return nullptr; - return iterator->second.type; - } + /*! + Stores the @c value of type @c Type to the offset registered for the field @c name. - /*! - @returns The number of instances of objects of the same type as @c name that sit consecutively in memory. - */ - size_t count_of(const std::string &name) const final { - const auto iterator = contents_.find(name); - if(iterator == contents_.end()) return 0; - return iterator->second.count; - } + It is the caller's responsibility to provide an appropriate type of data. + */ + void set(const std::string &name, const void *value, size_t offset) final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return; + assert(offset < iterator->second.count); + memcpy(reinterpret_cast(this) + iterator->second.offset + offset * iterator->second.size, value, iterator->second.size); + } - /*! - @returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct; - the empty list otherwise. - */ - std::vector values_for(const std::string &name) const final { - std::vector result; + /*! + @returns @c type_info for the field @c name. + */ + const std::type_info *type_of(const std::string &name) const final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return nullptr; + return iterator->second.type; + } - // Return an empty vector if this field isn't declared. - const auto type = type_of(name); - if(!type) return result; + /*! + @returns The number of instances of objects of the same type as @c name that sit consecutively in memory. + */ + size_t count_of(const std::string &name) const final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return 0; + return iterator->second.count; + } - // Also return an empty vector if this field isn't a registered enum. - const auto all_values = Enum::all_values(*type); - if(all_values.empty()) return result; + /*! + @returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct; + the empty list otherwise. + */ + std::vector values_for(const std::string &name) const final { + std::vector result; - // If no restriction is stored, return all values. - const auto permitted_values = permitted_enum_values_.find(name); - if(permitted_values == permitted_enum_values_.end()) return all_values; + // Return an empty vector if this field isn't declared. + const auto type = type_of(name); + if(!type) return result; - // Compile a vector of only those values the stored set indicates. - auto value = all_values.begin(); - auto flag = permitted_values->second.begin(); - while(value != all_values.end() && flag != permitted_values->second.end()) { - if(*flag) { - result.push_back(*value); - } - ++flag; - ++value; + // Also return an empty vector if this field isn't a registered enum. + const auto all_values = Enum::all_values(*type); + if(all_values.empty()) return result; + + // If no restriction is stored, return all values. + const auto permitted_values = permitted_enum_values_.find(name); + if(permitted_values == permitted_enum_values_.end()) return all_values; + + // Compile a vector of only those values the stored set indicates. + auto value = all_values.begin(); + auto flag = permitted_values->second.begin(); + while(value != all_values.end() && flag != permitted_values->second.end()) { + if(*flag) { + result.push_back(*value); } - - return result; + ++flag; + ++value; } - /*! - @returns A vector of all declared fields for this struct. - */ - std::vector all_keys() const final { - std::vector keys; - for(const auto &pair: contents_) { - keys.push_back(pair.first); + return result; + } + + /*! + @returns A vector of all declared fields for this struct. + */ + std::vector all_keys() const final { + std::vector keys; + for(const auto &pair: contents_) { + keys.push_back(pair.first); + } + return keys; + } + +protected: + /* + This interface requires reflective structs to declare all fields; + specifically they should call: + + declare_field(&field1, "field1"); + declare_field(&field2, "field2"); + + Fields are registered in class storage. So callers can use needs_declare() + to determine whether a class of this type has already established the + reflective fields. + */ + + /*! + Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct, + it'll be the struct that's exposed. + */ + template void declare(Type *t, const std::string &name) { + // If the declared item is a class, see whether it can be dynamically cast + // to a reflectable for emplacement. If so, exit early. + if constexpr (std::is_class()) { + if(declare_reflectable(t, name)) return; + } + + // If the declared item is an array, record it as a pointer to the + // first element plus a size. + if constexpr (std::is_array()) { + declare_emplace(&(*t)[0], name, sizeof(*t) / sizeof(*t[0])); + return; + } + + declare_emplace(t, name); + } + + /*! + If @c t is a previously-declared field that links to a declared enum then the variable + arguments provide a list of the acceptable values for that field. The list should be terminated + with a value of -1. + */ + template void limit_enum(Type *t, ...) { + const auto name = name_of(t); + if(name.empty()) return; + + // The default vector size of '8' isn't especially scientific, + // but I feel like it's a good choice. + std::vector permitted_values(8); + + va_list list; + va_start(list, t); + while(true) { + const int next = va_arg(list, int); + if(next < 0) break; + + if(permitted_values.size() <= size_t(next)) { + permitted_values.resize(permitted_values.size() << 1); } - return keys; + permitted_values[size_t(next)] = true; + } + va_end(list); + + permitted_enum_values_.emplace(name, permitted_values); + } + + /*! + @returns @c true if this subclass of @c Struct has not yet declared any fields. + */ + bool needs_declare() { + return contents_.empty(); + } + + /*! + Performs a reverse lookup from field to name. + */ + std::string name_of(void *field) { + const ssize_t offset = reinterpret_cast(field) - reinterpret_cast(this); + + auto iterator = contents_.begin(); + while(iterator != contents_.end()) { + if(iterator->second.offset == offset) break; + ++iterator; } - protected: - /* - This interface requires reflective structs to declare all fields; - specifically they should call: + if(iterator != contents_.end()) { + return iterator->first; + } else { + return std::string(); + } + } - declare_field(&field1, "field1"); - declare_field(&field2, "field2"); - - Fields are registered in class storage. So callers can use needs_declare() - to determine whether a class of this type has already established the - reflective fields. - */ - - /*! - Exposes the field pointed to by @c t for reflection as @c name. If @c t is itself a Reflection::Struct, - it'll be the struct that's exposed. - */ - template void declare(Type *t, const std::string &name) { - // If the declared item is a class, see whether it can be dynamically cast - // to a reflectable for emplacement. If so, exit early. - if constexpr (std::is_class()) { - if(declare_reflectable(t, name)) return; - } - - // If the declared item is an array, record it as a pointer to the - // first element plus a size. - if constexpr (std::is_array()) { - declare_emplace(&(*t)[0], name, sizeof(*t) / sizeof(*t[0])); - return; - } - - declare_emplace(t, name); +private: + template bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) { + if constexpr (std::is_base_of::value) { + Reflection::Struct *const str = static_cast(t); + declare_emplace(str, name); + return true; } - /*! - If @c t is a previously-declared field that links to a declared enum then the variable - arguments provide a list of the acceptable values for that field. The list should be terminated - with a value of -1. - */ - template void limit_enum(Type *t, ...) { - const auto name = name_of(t); - if(name.empty()) return; + return false; + } - // The default vector size of '8' isn't especially scientific, - // but I feel like it's a good choice. - std::vector permitted_values(8); + template void declare_emplace(Type *t, const std::string &name, size_t count = 1) { + contents_.emplace( + std::make_pair( + name, + Field(typeid(Type), reinterpret_cast(t) - reinterpret_cast(this), sizeof(Type), count) + )); + } - va_list list; - va_start(list, t); - while(true) { - const int next = va_arg(list, int); - if(next < 0) break; - - if(permitted_values.size() <= size_t(next)) { - permitted_values.resize(permitted_values.size() << 1); - } - permitted_values[size_t(next)] = true; - } - va_end(list); - - permitted_enum_values_.emplace(name, permitted_values); - } - - /*! - @returns @c true if this subclass of @c Struct has not yet declared any fields. - */ - bool needs_declare() { - return contents_.empty(); - } - - /*! - Performs a reverse lookup from field to name. - */ - std::string name_of(void *field) { - const ssize_t offset = reinterpret_cast(field) - reinterpret_cast(this); - - auto iterator = contents_.begin(); - while(iterator != contents_.end()) { - if(iterator->second.offset == offset) break; - ++iterator; - } - - if(iterator != contents_.end()) { - return iterator->first; - } else { - return std::string(); - } - } - - private: - template bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) { - if constexpr (std::is_base_of::value) { - Reflection::Struct *const str = static_cast(t); - declare_emplace(str, name); - return true; - } - - return false; - } - - template void declare_emplace(Type *t, const std::string &name, size_t count = 1) { - contents_.emplace( - std::make_pair( - name, - Field(typeid(Type), reinterpret_cast(t) - reinterpret_cast(this), sizeof(Type), count) - )); - } - - struct Field { - const std::type_info *type; - ssize_t offset; - size_t size; - size_t count; - Field(const std::type_info &type, ssize_t offset, size_t size, size_t count) : - type(&type), offset(offset), size(size), count(count) {} - }; - static inline std::unordered_map contents_; - static inline std::unordered_map> permitted_enum_values_; + struct Field { + const std::type_info *type; + ssize_t offset; + size_t size; + size_t count; + Field(const std::type_info &type, ssize_t offset, size_t size, size_t count) : + type(&type), offset(offset), size(size), count(count) {} + }; + static inline std::unordered_map contents_; + static inline std::unordered_map> permitted_enum_values_; }; diff --git a/SignalProcessing/FIRFilter.hpp b/SignalProcessing/FIRFilter.hpp index 4a5b62fc6..cf40a5aad 100644 --- a/SignalProcessing/FIRFilter.hpp +++ b/SignalProcessing/FIRFilter.hpp @@ -28,75 +28,87 @@ namespace SignalProcessing { smaller numbers permit a filter that operates more quickly and with less lag but less effectively. */ class FIRFilter { - private: - static constexpr float FixedMultiplier = 32767.0f; - static constexpr int FixedShift = 15; +private: + static constexpr float FixedMultiplier = 32767.0f; + static constexpr int FixedShift = 15; - public: - /*! A suggested default attenuation value. */ - constexpr static float DefaultAttenuation = 60.0f; - /*! - Creates an instance of @c FIRFilter. +public: + /*! A suggested default attenuation value. */ + constexpr static float DefaultAttenuation = 60.0f; + /*! + Creates an instance of @c FIRFilter. - @param number_of_taps The size of window for input data. - @param input_sample_rate The sampling rate of the input signal. - @param low_frequency The lowest frequency of signal to retain in the output. - @param high_frequency The highest frequency of signal to retain in the output. - @param attenuation The attenuation of the discarded frequencies. - */ - FIRFilter(std::size_t number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation = DefaultAttenuation); - FIRFilter(const std::vector &coefficients); + @param number_of_taps The size of window for input data. + @param input_sample_rate The sampling rate of the input signal. + @param low_frequency The lowest frequency of signal to retain in the output. + @param high_frequency The highest frequency of signal to retain in the output. + @param attenuation The attenuation of the discarded frequencies. + */ + FIRFilter( + std::size_t number_of_taps, + float input_sample_rate, + float low_frequency, + float high_frequency, + float attenuation = DefaultAttenuation + ); + FIRFilter(const std::vector &coefficients); - /*! - Applies the filter to one batch of input samples, returning the net result. + /*! + Applies the filter to one batch of input samples, returning the net result. - @param src The source buffer to apply the filter to. - @returns The result of applying the filter. - */ - inline short apply(const short *src, size_t stride = 1) const { - #ifdef USE_ACCELERATE - short result; - vDSP_dotpr_s1_15(filter_coefficients_.data(), 1, src, vDSP_Stride(stride), &result, filter_coefficients_.size()); - return result; - #else - int outputValue = 0; - for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) { - outputValue += filter_coefficients_[c] * src[c * stride]; - } - return short(outputValue >> FixedShift); - #endif - } + @param src The source buffer to apply the filter to. + @returns The result of applying the filter. + */ + inline short apply(const short *src, size_t stride = 1) const { + #ifdef USE_ACCELERATE + short result; + vDSP_dotpr_s1_15( + filter_coefficients_.data(), + 1, + src, + vDSP_Stride(stride), &result, filter_coefficients_.size() + ); + return result; + #else + int outputValue = 0; + for(std::size_t c = 0; c < filter_coefficients_.size(); ++c) { + outputValue += filter_coefficients_[c] * src[c * stride]; + } + return short(outputValue >> FixedShift); + #endif + } - /*! @returns The number of taps used by this filter. */ - inline std::size_t get_number_of_taps() const { - return filter_coefficients_.size(); - } + /*! @returns The number of taps used by this filter. */ + inline std::size_t get_number_of_taps() const { + return filter_coefficients_.size(); + } - /*! @returns The weighted coefficients that describe this filter. */ - std::vector get_coefficients() const; + /*! @returns The weighted coefficients that describe this filter. */ + std::vector get_coefficients() const; - /*! - @returns A filter that would have the effect of adding (and scaling) the outputs of the two filters. - Defined only if both have the same number of taps. - */ - FIRFilter operator+(const FIRFilter &) const; + /*! + @returns A filter that would have the effect of adding (and scaling) the outputs of the two filters. + Defined only if both have the same number of taps. + */ + FIRFilter operator+(const FIRFilter &) const; - /*! - @returns A filter that would have the effect of applying the two filters in succession. - Defined only if both have the same number of taps. - */ - FIRFilter operator*(const FIRFilter &) const; + /*! + @returns A filter that would have the effect of applying the two filters in succession. + Defined only if both have the same number of taps. + */ + FIRFilter operator*(const FIRFilter &) const; - /*! - @returns A filter that would have the opposite effect of this filter. - */ - FIRFilter operator-() const; + /*! + @returns A filter that would have the opposite effect of this filter. + */ + FIRFilter operator-() const; - private: - std::vector filter_coefficients_; +private: + std::vector filter_coefficients_; - static void coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, std::size_t numberOfTaps); - static float ino(float a); + static void coefficients_for_idealised_filter_response( + short *filterCoefficients, float *A, float attenuation, std::size_t numberOfTaps); + static float ino(float a); }; } diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index 82ce1356b..7f5ef1311 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -23,73 +23,73 @@ namespace SignalProcessing { that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600. */ class Stepper { - public: - /*! - Establishes a stepper with a one-to-one conversion rate. - */ - Stepper() : Stepper(1,1) {} +public: + /*! + Establishes a stepper with a one-to-one conversion rate. + */ + Stepper() : Stepper(1,1) {} - /*! - Establishes a stepper that will receive steps at the @c input_rate and dictate the number - of steps that should be taken at the @c output_rate. - */ - Stepper(uint64_t output_rate, uint64_t input_rate) : - accumulated_error_(-(int64_t(input_rate) << 1)), - input_rate_(input_rate), - output_rate_(output_rate), - whole_step_(output_rate / input_rate), - adjustment_up_(int64_t(output_rate % input_rate) << 1), - adjustment_down_(int64_t(input_rate) << 1) {} + /*! + Establishes a stepper that will receive steps at the @c input_rate and dictate the number + of steps that should be taken at the @c output_rate. + */ + Stepper(uint64_t output_rate, uint64_t input_rate) : + accumulated_error_(-(int64_t(input_rate) << 1)), + input_rate_(input_rate), + output_rate_(output_rate), + whole_step_(output_rate / input_rate), + adjustment_up_(int64_t(output_rate % input_rate) << 1), + adjustment_down_(int64_t(input_rate) << 1) {} - /*! - Advances one step at the input rate. + /*! + Advances one step at the input rate. - @returns the number of output steps. - */ - inline uint64_t step() { - uint64_t update = whole_step_; - accumulated_error_ += adjustment_up_; - if(accumulated_error_ > 0) { - update++; - accumulated_error_ -= adjustment_down_; - } - return update; + @returns the number of output steps. + */ + inline uint64_t step() { + uint64_t update = whole_step_; + accumulated_error_ += adjustment_up_; + if(accumulated_error_ > 0) { + update++; + accumulated_error_ -= adjustment_down_; } + return update; + } - /*! - Advances by @c number_of_steps steps at the input rate. + /*! + Advances by @c number_of_steps steps at the input rate. - @returns the number of output steps. - */ - inline uint64_t step(uint64_t number_of_steps) { - uint64_t update = whole_step_ * number_of_steps; - accumulated_error_ += adjustment_up_ * int64_t(number_of_steps); - if(accumulated_error_ > 0) { - update += 1 + uint64_t(accumulated_error_ / adjustment_down_); - accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_; - } - return update; + @returns the number of output steps. + */ + inline uint64_t step(uint64_t number_of_steps) { + uint64_t update = whole_step_ * number_of_steps; + accumulated_error_ += adjustment_up_ * int64_t(number_of_steps); + if(accumulated_error_ > 0) { + update += 1 + uint64_t(accumulated_error_ / adjustment_down_); + accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_; } + return update; + } - /*! - @returns the output rate. - */ - inline uint64_t get_output_rate() { - return output_rate_; - } + /*! + @returns the output rate. + */ + inline uint64_t get_output_rate() const { + return output_rate_; + } - /*! - @returns the input rate. - */ - inline uint64_t get_input_rate() { - return input_rate_; - } + /*! + @returns the input rate. + */ + inline uint64_t get_input_rate() const { + return input_rate_; + } - private: - int64_t accumulated_error_; - uint64_t input_rate_, output_rate_; - uint64_t whole_step_; - int64_t adjustment_up_, adjustment_down_; +private: + int64_t accumulated_error_; + uint64_t input_rate_, output_rate_; + uint64_t whole_step_; + int64_t adjustment_up_, adjustment_down_; }; } diff --git a/Storage/Cartridge/Cartridge.hpp b/Storage/Cartridge/Cartridge.hpp index 2403cdd1d..0e8cc27ca 100644 --- a/Storage/Cartridge/Cartridge.hpp +++ b/Storage/Cartridge/Cartridge.hpp @@ -24,59 +24,59 @@ namespace Storage::Cartridge { making the base class 100% descriptive. */ class Cartridge { - public: - struct Segment { - Segment(size_t start_address, size_t end_address, std::vector &&data) : - start_address(start_address), end_address(end_address), data(data) {} +public: + struct Segment { + Segment(size_t start_address, size_t end_address, std::vector &&data) : + start_address(start_address), end_address(end_address), data(data) {} - Segment(size_t start_address, size_t end_address, const std::vector &data) : - start_address(start_address), end_address(end_address), data(data) {} + Segment(size_t start_address, size_t end_address, const std::vector &data) : + start_address(start_address), end_address(end_address), data(data) {} - Segment(size_t start_address, std::vector &&data) : - Segment(start_address, start_address + data.size(), data) {} + Segment(size_t start_address, std::vector &&data) : + Segment(start_address, start_address + data.size(), data) {} - Segment(size_t start_address, const std::vector &data) : - Segment(start_address, start_address + data.size(), data) {} + Segment(size_t start_address, const std::vector &data) : + Segment(start_address, start_address + data.size(), data) {} - Segment(Segment &&segment) : - start_address(segment.start_address), - end_address(segment.end_address), - data(std::move(segment.data)) {} + Segment(Segment &&segment) : + start_address(segment.start_address), + end_address(segment.end_address), + data(std::move(segment.data)) {} - Segment(const Segment &segment) : - start_address(segment.start_address), - end_address(segment.end_address), - data(segment.data) {} + Segment(const Segment &segment) : + start_address(segment.start_address), + end_address(segment.end_address), + data(segment.data) {} - /// Indicates that an address is unknown. - static const size_t UnknownAddress; + /// Indicates that an address is unknown. + static const size_t UnknownAddress; - /// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress. - size_t start_address; - /*! - The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal - to start_address + data_length due to potential paging. - */ - size_t end_address; + /// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress. + size_t start_address; + /*! + The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal + to start_address + data_length due to potential paging. + */ + size_t end_address; - /*! - The data contents for this segment. If @c start_address and @c end_address are suppled then - the first end_address - start_address bytes will be those initially visible. The size will - not necessarily be the same as @c end_address - @c start_address due to potential paging. - */ - std::vector data; - }; + /*! + The data contents for this segment. If @c start_address and @c end_address are suppled then + the first end_address - start_address bytes will be those initially visible. The size will + not necessarily be the same as @c end_address - @c start_address due to potential paging. + */ + std::vector data; + }; - const std::vector &get_segments() const { - return segments_; - } - virtual ~Cartridge() = default; + const std::vector &get_segments() const { + return segments_; + } + virtual ~Cartridge() = default; - Cartridge() = default; - Cartridge(const std::vector &segments) : segments_(segments) {} + Cartridge() = default; + Cartridge(const std::vector &segments) : segments_(segments) {} - protected: - std::vector segments_; +protected: + std::vector segments_; }; } diff --git a/Storage/Cartridge/Formats/BinaryDump.hpp b/Storage/Cartridge/Formats/BinaryDump.hpp index 0c45f6ac5..fd35ab88c 100644 --- a/Storage/Cartridge/Formats/BinaryDump.hpp +++ b/Storage/Cartridge/Formats/BinaryDump.hpp @@ -15,12 +15,12 @@ namespace Storage::Cartridge { class BinaryDump : public Cartridge { - public: - BinaryDump(const std::string &file_name); +public: + BinaryDump(const std::string &file_name); - enum { - ErrorNotAccessible - }; + enum { + ErrorNotAccessible + }; }; } diff --git a/Storage/Cartridge/Formats/PRG.hpp b/Storage/Cartridge/Formats/PRG.hpp index 0edb77a9b..bf0cc45de 100644 --- a/Storage/Cartridge/Formats/PRG.hpp +++ b/Storage/Cartridge/Formats/PRG.hpp @@ -15,12 +15,12 @@ namespace Storage::Cartridge { class PRG : public Cartridge { - public: - PRG(const std::string &file_name); +public: + PRG(const std::string &file_name); - enum { - ErrorNotROM - }; + enum { + ErrorNotROM + }; }; } diff --git a/Storage/Disk/Controller/DiskController.hpp b/Storage/Disk/Controller/DiskController.hpp index a1a1a4a39..2213cd1b0 100644 --- a/Storage/Disk/Controller/DiskController.hpp +++ b/Storage/Disk/Controller/DiskController.hpp @@ -30,139 +30,139 @@ class Controller: public ClockingHint::Source, private Drive::EventDelegate, private ClockingHint::Observer { - protected: - /*! - Constructs a @c Controller that will be run at @c clock_rate. - */ - Controller(Cycles clock_rate); +protected: + /*! + Constructs a @c Controller that will be run at @c clock_rate. + */ + Controller(Cycles clock_rate); - /*! - Communicates to the PLL the expected length of a bit as a fraction of a second. - */ - void set_expected_bit_length(Time bit_length); + /*! + Communicates to the PLL the expected length of a bit as a fraction of a second. + */ + void set_expected_bit_length(Time bit_length); - /*! - Advances the drive by @c number_of_cycles cycles. - */ - void run_for(const Cycles cycles); + /*! + Advances the drive by @c number_of_cycles cycles. + */ + void run_for(const Cycles cycles); - /*! - 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. + /*! + 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); + 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); - /*! - Adds a new drive to the drive list, returning its index. - */ - template size_t emplace_drive(Args&&... args) { - drives_.emplace_back(new Drive(std::forward(args)...)); - drives_.back()->set_clocking_hint_observer(this); - return drives_.size() - 1; + /*! + Adds a new drive to the drive list, returning its index. + */ + template size_t emplace_drive(Args&&... args) { + drives_.emplace_back(new Drive(std::forward(args)...)); + drives_.back()->set_clocking_hint_observer(this); + return drives_.size() - 1; + } + + /*! + Adds @c count new drives to the drive list, returning the index of the final one added. + */ + template size_t emplace_drives(size_t count, Args&&... args) { + while(count--) { + emplace_drive(std::forward(args)...); } + return drives_.size() - 1; + } - /*! - Adds @c count new drives to the drive list, returning the index of the final one added. - */ - template size_t emplace_drives(size_t count, Args&&... args) { - while(count--) { - emplace_drive(std::forward(args)...); - } - return drives_.size() - 1; + /*! + Should be implemented by subclasses; communicates each bit that the PLL recognises. + */ + virtual void process_input_bit(int value) = 0; + + /*! + Should be implemented by subclasses; communicates that the index hole has been reached. + */ + virtual void process_index_hole() = 0; + + /*! + Should be implemented by subclasses if they implement writing; communicates that + all bits supplied to write_bit have now been written. + */ + virtual void process_write_completed() override; + + /*! + Puts the controller and the drive returned by get_drive() into write mode, supplying to + the drive the current bit length. + + While the controller is in write mode it disconnects the PLL. So subclasses will not + receive any calls to @c process_input_bit. + + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. + */ + void begin_writing(bool clamp_to_index_hole); + + /*! + Puts the drive returned by get_drive() out of write mode, and marks the controller + as no longer being in write mode. + */ + void end_writing(); + + /*! + @returns @c true if the controller is in reading mode; @c false otherwise. + */ + bool is_reading(); + + /*! + Returns the connected drive or, if none is connected, an invented one. No guarantees are + made about the lifetime or the exclusivity of the invented drive. + */ + Drive &get_drive(); + const Drive &get_drive() const; + + Drive &get_drive(size_t index) { + return *drives_[index]; + } + const Drive &get_drive(size_t index) const { + return *drives_[index]; + } + + void for_all_drives(const std::function &func) { + size_t index = 0; + for(auto &drive: drives_) { + func(*drive, index); + ++index; } + } - /*! - Should be implemented by subclasses; communicates each bit that the PLL recognises. - */ - virtual void process_input_bit(int value) = 0; + /*! + As per ClockingHint::Source. + */ + ClockingHint::Preference preferred_clocking() const override; - /*! - Should be implemented by subclasses; communicates that the index hole has been reached. - */ - virtual void process_index_hole() = 0; +private: + Time bit_length_; + Cycles::IntType clock_rate_multiplier_ = 1; + Cycles::IntType clock_rate_ = 1; - /*! - Should be implemented by subclasses if they implement writing; communicates that - all bits supplied to write_bit have now been written. - */ - virtual void process_write_completed() override; + bool is_reading_ = true; - /*! - Puts the controller and the drive returned by get_drive() into write mode, supplying to - the drive the current bit length. + DigitalPhaseLockedLoop pll_; + friend DigitalPhaseLockedLoop; - While the controller is in write mode it disconnects the PLL. So subclasses will not - receive any calls to @c process_input_bit. + Drive empty_drive_; + std::vector> drives_; + Drive *drive_; + int drive_selection_mask_ = 0xff; - @param clamp_to_index_hole If @c true then writing will automatically be truncated by - the index hole. Writing will continue over the index hole otherwise. - */ - void begin_writing(bool clamp_to_index_hole); + // ClockingHint::Observer. + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final; - /*! - Puts the drive returned by get_drive() out of write mode, and marks the controller - as no longer being in write mode. - */ - void end_writing(); + // for Drive::EventDelegate + void process_event(const Drive::Event &event) final; + void advance(const Cycles cycles) final; - /*! - @returns @c true if the controller is in reading mode; @c false otherwise. - */ - bool is_reading(); - - /*! - Returns the connected drive or, if none is connected, an invented one. No guarantees are - made about the lifetime or the exclusivity of the invented drive. - */ - Drive &get_drive(); - const Drive &get_drive() const; - - Drive &get_drive(size_t index) { - return *drives_[index]; - } - const Drive &get_drive(size_t index) const { - return *drives_[index]; - } - - void for_all_drives(const std::function &func) { - size_t index = 0; - for(auto &drive: drives_) { - func(*drive, index); - ++index; - } - } - - /*! - As per ClockingHint::Source. - */ - ClockingHint::Preference preferred_clocking() const override; - - private: - Time bit_length_; - Cycles::IntType clock_rate_multiplier_ = 1; - Cycles::IntType clock_rate_ = 1; - - bool is_reading_ = true; - - DigitalPhaseLockedLoop pll_; - friend DigitalPhaseLockedLoop; - - Drive empty_drive_; - std::vector> drives_; - Drive *drive_; - int drive_selection_mask_ = 0xff; - - // ClockingHint::Observer. - void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final; - - // for Drive::EventDelegate - void process_event(const Drive::Event &event) final; - void advance(const Cycles cycles) final; - - // to satisfy DigitalPhaseLockedLoop::Delegate - void digital_phase_locked_loop_output_bit(int value); + // to satisfy DigitalPhaseLockedLoop::Delegate + void digital_phase_locked_loop_output_bit(int value); }; } diff --git a/Storage/Disk/Controller/MFMDiskController.hpp b/Storage/Disk/Controller/MFMDiskController.hpp index 3cc449ef1..7d768987b 100644 --- a/Storage/Disk/Controller/MFMDiskController.hpp +++ b/Storage/Disk/Controller/MFMDiskController.hpp @@ -20,148 +20,148 @@ namespace Storage::Disk { being able to post event messages to subclasses. */ class MFMController: public Controller { - public: - MFMController(Cycles clock_rate); +public: + MFMController(Cycles clock_rate); - protected: - /// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content. - void set_is_double_density(bool); +protected: + /// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content. + void set_is_double_density(bool); - /// @returns @c true if currently decoding MFM content; @c false otherwise. - bool get_is_double_density(); + /// @returns @c true if currently decoding MFM content; @c false otherwise. + bool get_is_double_density(); - enum DataMode { - /// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data. - Scanning, - /// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks. - Reading, - /// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty. - Writing - }; - /// Sets the current data mode. - void set_data_mode(DataMode); + enum DataMode { + /// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data. + Scanning, + /// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks. + Reading, + /// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty. + Writing + }; + /// Sets the current data mode. + void set_data_mode(DataMode); - /*! - Describes a token found in the incoming PLL bit stream. Tokens can be one of: + /*! + Describes a token found in the incoming PLL bit stream. Tokens can be one of: - Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole; - ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc. - Data: the pattern that begins a data section, i.e. sector contents. - DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents. + Index: the bit pattern usually encoded at the start of a track to denote the position of the index hole; + ID: the pattern that begins an ID section, i.e. a sector header, announcing sector number, track number, etc. + Data: the pattern that begins a data section, i.e. sector contents. + DeletedData: the pattern that begins a deleted data section, i.e. deleted sector contents. - Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types - of token listed above; this class combines notification of that mark and the distinct index sync mark. - Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an - announcement of sync followed by an announcement of one of the above four types of token. + Sync: MFM only; the same synchronisation mark is used in MFM to denote the bottom three of the four types + of token listed above; this class combines notification of that mark and the distinct index sync mark. + Both are followed by a byte to indicate type. When scanning an MFM stream, subclasses will receive an + announcement of sync followed by an announcement of one of the above four types of token. - Byte: reports reading of an ordinary byte, with expected timing bits. + Byte: reports reading of an ordinary byte, with expected timing bits. - When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token - is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as - spurious sync signals can otherwise be found in ordinary data, causing framing errors. - */ - struct Token { - enum Type { - Index, ID, Data, DeletedData, Sync, Byte - } type; - uint8_t byte_value; - }; - /// @returns The most-recently read token from the surface of the disk. - Token get_latest_token(); + When the data mode is set to 'reading', only Byte tokens are returned; detection of the other kinds of token + is suppressed. Controllers will likely want to switch data mode when receiving ID and sector contents, as + spurious sync signals can otherwise be found in ordinary data, causing framing errors. + */ + struct Token { + enum Type { + Index, ID, Data, DeletedData, Sync, Byte + } type; + uint8_t byte_value; + }; + /// @returns The most-recently read token from the surface of the disk. + Token get_latest_token(); - /// @returns The controller's CRC generator. This is automatically fed during reading. - CRC::CCITT &get_crc_generator(); + /// @returns The controller's CRC generator. This is automatically fed during reading. + CRC::CCITT &get_crc_generator(); - // Events - enum class Event: int { - Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details. - IndexHole = (1 << 1), // Indicates the passing of a physical index hole. - DataWritten = (1 << 2), // Indicates that all queued bits have been written - }; + // Events + enum class Event: int { + Token = (1 << 0), // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details. + IndexHole = (1 << 1), // Indicates the passing of a physical index hole. + DataWritten = (1 << 2), // Indicates that all queued bits have been written + }; - /*! - Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream. - Therefore it is called to announce when: + /*! + Subclasses should implement this. It is called every time a new @c Event is discovered in the incoming data stream. + Therefore it is called to announce when: - (i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data. - (ii) the index hole passes; or - (iii) the queue of data to be written has been exhausted. - */ - virtual void posit_event(int type) = 0; + (i) a new token is discovered in the incoming stream: an index, ID, data or deleted data, a sync mark or a new byte of data. + (ii) the index hole passes; or + (iii) the queue of data to be written has been exhausted. + */ + virtual void posit_event(int type) = 0; - /*! - Encodes @c bit according to the current single/double density mode and adds it - to the controller's write buffer. - */ - void write_bit(int bit); + /*! + Encodes @c bit according to the current single/double density mode and adds it + to the controller's write buffer. + */ + void write_bit(int bit); - /*! - Encodes @c byte according to the current single/double density mode and adds it - to the controller's write buffer. - */ - void write_byte(uint8_t byte); + /*! + Encodes @c byte according to the current single/double density mode and adds it + to the controller's write buffer. + */ + void write_byte(uint8_t byte); - /*! - Serialises @c value into the controller's write buffer without adjustment. - */ - void write_raw_short(uint16_t value); + /*! + Serialises @c value into the controller's write buffer without adjustment. + */ + void write_raw_short(uint16_t value); - /*! - Gets the current value of the CRC generator and makes two calls to @c write_byte, to - write first its higher-value byte and then its lower. - */ - void write_crc(); + /*! + Gets the current value of the CRC generator and makes two calls to @c write_byte, to + write first its higher-value byte and then its lower. + */ + void write_crc(); - /*! - Calls @c write_byte with @c value, @c quantity times. - */ - void write_n_bytes(int quantity, uint8_t value); + /*! + Calls @c write_byte with @c value, @c quantity times. + */ + void write_n_bytes(int quantity, uint8_t value); - /*! - Writes everything that should per the spec appear prior to the address contained - in an ID mark (i.e. proper gaps and the ID mark) and appropriate seeds the CRC generator. - */ - void write_id_joiner(); + /*! + Writes everything that should per the spec appear prior to the address contained + in an ID mark (i.e. proper gaps and the ID mark) and appropriate seeds the CRC generator. + */ + void write_id_joiner(); - /*! - Writes at most what should, per the spec, appear after the ID's CRC, up to and - including the mark that indicates the beginning of data, appropriately seeding - the CRC generator; if @c skip_first_gap is set then the initial gap after the - CRC isn't written. - */ - void write_id_data_joiner(bool is_deleted, bool skip_first_gap); + /*! + Writes at most what should, per the spec, appear after the ID's CRC, up to and + including the mark that indicates the beginning of data, appropriately seeding + the CRC generator; if @c skip_first_gap is set then the initial gap after the + CRC isn't written. + */ + void write_id_data_joiner(bool is_deleted, bool skip_first_gap); - /*! - Writes the gap expected after a sector's data CRC and before the beginning of the - next ID joiner. - */ - void write_post_data_gap(); + /*! + Writes the gap expected after a sector's data CRC and before the beginning of the + next ID joiner. + */ + void write_post_data_gap(); - /*! - Writes everything that should, per the spec, following the index hole and prior - to any sectors. - */ - void write_start_of_track(); + /*! + Writes everything that should, per the spec, following the index hole and prior + to any sectors. + */ + void write_start_of_track(); - private: - // Storage::Disk::Controller - virtual void process_input_bit(int value); - virtual void process_index_hole(); - virtual void process_write_completed(); +private: + // Storage::Disk::Controller + virtual void process_input_bit(int value); + virtual void process_index_hole(); + virtual void process_write_completed(); - // Reading state. - Token latest_token_; - Encodings::MFM::Shifter shifter_; + // Reading state. + Token latest_token_; + Encodings::MFM::Shifter shifter_; - // input configuration - bool is_double_density_; - DataMode data_mode_ = DataMode::Scanning; + // input configuration + bool is_double_density_; + DataMode data_mode_ = DataMode::Scanning; - // writing - int last_bit_; + // writing + int last_bit_; - // CRC generator - CRC::CCITT crc_generator_; + // CRC generator + CRC::CCITT crc_generator_; }; } diff --git a/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp b/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp index 1b053e5d6..ce245e40d 100644 --- a/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp +++ b/Storage/Disk/DPLL/DigitalPhaseLockedLoop.hpp @@ -24,110 +24,110 @@ namespace Storage { @c length_of_history The number of historic pulses to consider in locking to phase. */ template class DigitalPhaseLockedLoop { - public: - /*! - Instantiates a @c DigitalPhaseLockedLoop. +public: + /*! + Instantiates a @c DigitalPhaseLockedLoop. - @param clocks_per_bit The expected number of cycles between each bit of input. - */ - DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) : - bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {} + @param clocks_per_bit The expected number of cycles between each bit of input. + */ + DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) : + bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {} - /*! - Changes the expected window length. - */ - void set_clocks_per_bit(int clocks_per_bit) { - window_length_ = clocks_per_bit_ = clocks_per_bit; + /*! + Changes the expected window length. + */ + void set_clocks_per_bit(int clocks_per_bit) { + window_length_ = clocks_per_bit_ = clocks_per_bit; + } + + /*! + Runs the loop, impliedly posting no pulses during that period. + + @c number_of_cycles The time to run the loop for. + */ + void run_for(const Cycles cycles) { + offset_ += cycles.as_integral(); + phase_ += cycles.as_integral(); + if(phase_ >= window_length_) { + auto windows_crossed = phase_ / window_length_; + + // Check whether this triggers any 0s. + if(window_was_filled_) --windows_crossed; + for(int c = 0; c < windows_crossed; c++) + bit_handler_.digital_phase_locked_loop_output_bit(0); + + window_was_filled_ = false; + phase_ %= window_length_; } + } - /*! - Runs the loop, impliedly posting no pulses during that period. - - @c number_of_cycles The time to run the loop for. - */ - void run_for(const Cycles cycles) { - offset_ += cycles.as_integral(); - phase_ += cycles.as_integral(); - if(phase_ >= window_length_) { - auto windows_crossed = phase_ / window_length_; - - // Check whether this triggers any 0s. - if(window_was_filled_) --windows_crossed; - for(int c = 0; c < windows_crossed; c++) - bit_handler_.digital_phase_locked_loop_output_bit(0); - - window_was_filled_ = false; - phase_ %= window_length_; - } + /*! + Announces a pulse at the current time. + */ + void add_pulse() { + if(!window_was_filled_) { + bit_handler_.digital_phase_locked_loop_output_bit(1); + window_was_filled_ = true; + post_phase_offset(phase_, offset_); + offset_ = 0; } + } - /*! - Announces a pulse at the current time. - */ - void add_pulse() { - if(!window_was_filled_) { - bit_handler_.digital_phase_locked_loop_output_bit(1); - window_was_filled_ = true; - post_phase_offset(phase_, offset_); - offset_ = 0; - } - } +private: + BitHandler &bit_handler_; - private: - BitHandler &bit_handler_; + void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) { + // Erase the effect of whatever is currently in this slot. + total_divisor_ -= offset_history_[offset_history_pointer_].divisor; + total_spacing_ -= offset_history_[offset_history_pointer_].spacing; - void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) { - // Erase the effect of whatever is currently in this slot. - total_divisor_ -= offset_history_[offset_history_pointer_].divisor; - total_spacing_ -= offset_history_[offset_history_pointer_].spacing; + // Fill in the new fields. + const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1)); + offset_history_[offset_history_pointer_].divisor = multiple; + offset_history_[offset_history_pointer_].spacing = new_offset; - // Fill in the new fields. - const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1)); - offset_history_[offset_history_pointer_].divisor = multiple; - offset_history_[offset_history_pointer_].spacing = new_offset; + // Add in the new values; + total_divisor_ += offset_history_[offset_history_pointer_].divisor; + total_spacing_ += offset_history_[offset_history_pointer_].spacing; - // Add in the new values; - total_divisor_ += offset_history_[offset_history_pointer_].divisor; - total_spacing_ += offset_history_[offset_history_pointer_].spacing; - - // Advance the write slot. - offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size(); + // Advance the write slot. + offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size(); #ifndef NDEBUG - Cycles::IntType td = 0, ts = 0; - for(auto offset: offset_history_) { - td += offset.divisor; - ts += offset.spacing; - } - assert(ts == total_spacing_); - assert(td == total_divisor_); + Cycles::IntType td = 0, ts = 0; + for(auto offset: offset_history_) { + td += offset.divisor; + ts += offset.spacing; + } + assert(ts == total_spacing_); + assert(td == total_divisor_); #endif - // In net: use an unweighted average of the stored offsets to compute current window size, - // bucketing them by rounding to the nearest multiple of the base clocks per bit - window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1)); + // In net: use an unweighted average of the stored offsets to compute current window size, + // bucketing them by rounding to the nearest multiple of the base clocks per bit + window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1)); - // Also apply a difference to phase, use a simple spring mechanism as a lowpass filter. - const auto error = new_phase - (window_length_ >> 1); - phase_ -= (error + 1) >> 1; - } + // Also apply a difference to phase, use a simple spring mechanism as a lowpass filter. + const auto error = new_phase - (window_length_ >> 1); + phase_ -= (error + 1) >> 1; + } - struct LoggedOffset { - Cycles::IntType divisor = 1, spacing = 1; - }; - std::array offset_history_; - std::size_t offset_history_pointer_ = 0; + struct LoggedOffset { + Cycles::IntType divisor = 1, spacing = 1; + }; + std::array offset_history_; + std::size_t offset_history_pointer_ = 0; - Cycles::IntType total_spacing_ = length_of_history; - Cycles::IntType total_divisor_ = length_of_history; + Cycles::IntType total_spacing_ = length_of_history; + Cycles::IntType total_divisor_ = length_of_history; - Cycles::IntType phase_ = 0; - Cycles::IntType window_length_ = 0; + Cycles::IntType phase_ = 0; + Cycles::IntType window_length_ = 0; - Cycles::IntType offset_ = 0; - bool window_was_filled_ = false; + Cycles::IntType offset_ = 0; + bool window_was_filled_ = false; - int clocks_per_bit_ = 0; + int clocks_per_bit_ = 0; }; } diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index 00afb3431..8bdacbeca 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -23,48 +23,48 @@ namespace Storage::Disk { Models a flopy disk. */ class Disk { - public: - virtual ~Disk() = default; +public: + virtual ~Disk() = default; - /*! - @returns the number of discrete positions that this disk uses to model its complete surface area. + /*! + @returns the number of discrete positions that this disk uses to model its complete surface area. - This is not necessarily a track count. There is no implicit guarantee that every position will - return a distinct track, or, e.g. if the media is holeless, will return any track at all. - */ - virtual HeadPosition get_maximum_head_position() = 0; + This is not necessarily a track count. There is no implicit guarantee that every position will + return a distinct track, or, e.g. if the media is holeless, will return any track at all. + */ + virtual HeadPosition get_maximum_head_position() = 0; - /*! - @returns the number of heads (and, therefore, impliedly surfaces) available on this disk. - */ - virtual int get_head_count() = 0; + /*! + @returns the number of heads (and, therefore, impliedly surfaces) available on this disk. + */ + virtual int get_head_count() = 0; - /*! - @returns the @c Track at @c position underneath @c head if there are any detectable events there; - returns @c nullptr otherwise. - */ - virtual std::shared_ptr get_track_at_position(Track::Address address) = 0; + /*! + @returns the @c Track at @c position underneath @c head if there are any detectable events there; + returns @c nullptr otherwise. + */ + virtual std::shared_ptr get_track_at_position(Track::Address address) = 0; - /*! - Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only. - */ - virtual void set_track_at_position(Track::Address address, const std::shared_ptr &track) = 0; + /*! + Replaces the Track at position @c position underneath @c head with @c track. Ignored if this disk is read-only. + */ + virtual void set_track_at_position(Track::Address address, const std::shared_ptr &track) = 0; - /*! - Provides a hint that no further tracks are likely to be written for a while. - */ - virtual void flush_tracks() = 0; + /*! + Provides a hint that no further tracks are likely to be written for a while. + */ + virtual void flush_tracks() = 0; - /*! - @returns whether the disk image is read only. Defaults to @c true if not overridden. - */ - virtual bool get_is_read_only() = 0; + /*! + @returns whether the disk image is read only. Defaults to @c true if not overridden. + */ + virtual bool get_is_read_only() = 0; - /*! - @returns @c true if the tracks at the two addresses are different. @c false if they are the same track. - This can avoid some degree of work when disk images offer sub-head-position precision. - */ - virtual bool tracks_differ(Track::Address, Track::Address) = 0; + /*! + @returns @c true if the tracks at the two addresses are different. @c false if they are the same track. + This can avoid some degree of work when disk images offer sub-head-position precision. + */ + virtual bool tracks_differ(Track::Address, Track::Address) = 0; }; } diff --git a/Storage/Disk/DiskImage/DiskImage.hpp b/Storage/Disk/DiskImage/DiskImage.hpp index fd78f8595..8d2006b58 100644 --- a/Storage/Disk/DiskImage/DiskImage.hpp +++ b/Storage/Disk/DiskImage/DiskImage.hpp @@ -30,48 +30,48 @@ enum class Error { if it matches the media. */ class DiskImage { - public: - virtual ~DiskImage() = default; +public: + virtual ~DiskImage() = default; - /*! - @returns the distance at which there stops being any further content. + /*! + @returns the distance at which there stops being any further content. - This is not necessarily a track count. There is no implicit guarantee that every position will - return a distinct track, or, e.g. if the media is holeless, will return any track at all. - */ - virtual HeadPosition get_maximum_head_position() = 0; + This is not necessarily a track count. There is no implicit guarantee that every position will + return a distinct track, or, e.g. if the media is holeless, will return any track at all. + */ + virtual HeadPosition get_maximum_head_position() = 0; - /*! - @returns the number of heads (and, therefore, impliedly surfaces) available on this disk. - */ - virtual int get_head_count() { return 1; } + /*! + @returns the number of heads (and, therefore, impliedly surfaces) available on this disk. + */ + virtual int get_head_count() { return 1; } - /*! - @returns the @c Track at @c position underneath @c head if there are any detectable events there; - returns @c nullptr otherwise. - */ - virtual std::shared_ptr get_track_at_position(Track::Address address) = 0; + /*! + @returns the @c Track at @c position underneath @c head if there are any detectable events there; + returns @c nullptr otherwise. + */ + virtual std::shared_ptr get_track_at_position(Track::Address address) = 0; - /*! - Replaces the Tracks indicated by the map, that maps from physical address to track content. - */ - virtual void set_tracks(const std::map> &) {} + /*! + Replaces the Tracks indicated by the map, that maps from physical address to track content. + */ + virtual void set_tracks(const std::map> &) {} - /*! - Communicates that it is likely to be a while before any more tracks are written. - */ - virtual void flush_tracks() {} + /*! + Communicates that it is likely to be a while before any more tracks are written. + */ + virtual void flush_tracks() {} - /*! - @returns whether the disk image is read only. Defaults to @c true if not overridden. - */ - virtual bool get_is_read_only() { return true; } + /*! + @returns whether the disk image is read only. Defaults to @c true if not overridden. + */ + virtual bool get_is_read_only() { return true; } - /*! - @returns @c true if the tracks at the two addresses are different. @c false if they are the same track. - This can avoid some degree of work when disk images offer sub-head-position precision. - */ - virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; } + /*! + @returns @c true if the tracks at the two addresses are different. @c false if they are the same track. + This can avoid some degree of work when disk images offer sub-head-position precision. + */ + virtual bool tracks_differ(Track::Address lhs, Track::Address rhs) { return lhs != rhs; } }; class DiskImageHolderBase: public Disk { @@ -90,29 +90,29 @@ class DiskImageHolderBase: public Disk { the underlying image doesn't implement TypeDistinguisher, or else to pass the call along. */ template class DiskImageHolder: public DiskImageHolderBase, public TargetPlatform::TypeDistinguisher { - public: - template DiskImageHolder(Ts&&... args) : - disk_image_(args...) {} - ~DiskImageHolder(); +public: + template DiskImageHolder(Ts&&... args) : + disk_image_(args...) {} + ~DiskImageHolder(); - HeadPosition get_maximum_head_position(); - int get_head_count(); - std::shared_ptr get_track_at_position(Track::Address address); - void set_track_at_position(Track::Address address, const std::shared_ptr &track); - void flush_tracks(); - bool get_is_read_only(); - bool tracks_differ(Track::Address lhs, Track::Address rhs); + HeadPosition get_maximum_head_position(); + int get_head_count(); + std::shared_ptr get_track_at_position(Track::Address address); + void set_track_at_position(Track::Address address, const std::shared_ptr &track); + void flush_tracks(); + bool get_is_read_only(); + bool tracks_differ(Track::Address lhs, Track::Address rhs); - private: - T disk_image_; +private: + T disk_image_; - TargetPlatform::Type target_platform_type() final { - if constexpr (std::is_base_of::value) { - return static_cast(&disk_image_)->target_platform_type(); - } else { - return TargetPlatform::Type(~0); - } + TargetPlatform::Type target_platform_type() final { + if constexpr (std::is_base_of::value) { + return static_cast(&disk_image_)->target_platform_type(); + } else { + return TargetPlatform::Type(~0); } + } }; #include "DiskImageImplementation.hpp" diff --git a/Storage/Disk/DiskImage/Formats/2MG.hpp b/Storage/Disk/DiskImage/Formats/2MG.hpp index 318d4ff70..ebd5590d5 100644 --- a/Storage/Disk/DiskImage/Formats/2MG.hpp +++ b/Storage/Disk/DiskImage/Formats/2MG.hpp @@ -28,9 +28,10 @@ namespace Storage::Disk { */ class Disk2MG { - public: - using DiskOrMassStorageDevice = std::variant; - static DiskOrMassStorageDevice open(const std::string &file_name); +public: + using DiskOrMassStorageDevice = + std::variant; + static DiskOrMassStorageDevice open(const std::string &file_name); }; } diff --git a/Storage/Disk/DiskImage/Formats/AcornADF.hpp b/Storage/Disk/DiskImage/Formats/AcornADF.hpp index 68a446ec7..ce8f1b906 100644 --- a/Storage/Disk/DiskImage/Formats/AcornADF.hpp +++ b/Storage/Disk/DiskImage/Formats/AcornADF.hpp @@ -18,23 +18,23 @@ namespace Storage::Disk { Provides a @c Disk containing an ADF disk image: a decoded sector dump of an Acorn ADFS disk. */ class AcornADF: public MFMSectorDump { - public: - /*! - Construct an @c AcornADF containing content from the file with name @c file_name. +public: + /*! + Construct an @c AcornADF containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image. - */ - AcornADF(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image. + */ + AcornADF(const std::string &file_name); - HeadPosition get_maximum_head_position() final; - int get_head_count() final; + HeadPosition get_maximum_head_position() final; + int get_head_count() final; - private: - long get_file_offset_for_position(Track::Address address) final; - int head_count_ = 1; - uint8_t sector_size_ = 1; - int sectors_per_track_ = 16; +private: + long get_file_offset_for_position(Track::Address) final; + int head_count_ = 1; + uint8_t sector_size_ = 1; + int sectors_per_track_ = 16; }; } diff --git a/Storage/Disk/DiskImage/Formats/AmigaADF.hpp b/Storage/Disk/DiskImage/Formats/AmigaADF.hpp index cfc85836c..2668eb9a5 100644 --- a/Storage/Disk/DiskImage/Formats/AmigaADF.hpp +++ b/Storage/Disk/DiskImage/Formats/AmigaADF.hpp @@ -19,23 +19,23 @@ namespace Storage::Disk { but the Amiga doesn't use IBM-style sector demarcation. */ class AmigaADF: public DiskImage { - public: - /*! - Construct an @c AmigaADF containing content from the file with name @c file_name. +public: + /*! + Construct an @c AmigaADF containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image. - */ - AmigaADF(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image. + */ + AmigaADF(const std::string &file_name); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - std::shared_ptr get_track_at_position(Track::Address address) final; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + std::shared_ptr get_track_at_position(Track::Address) final; - private: - Storage::FileHolder file_; - long get_file_offset_for_position(Track::Address address); +private: + Storage::FileHolder file_; + long get_file_offset_for_position(Track::Address); }; diff --git a/Storage/Disk/DiskImage/Formats/AppleDSK.hpp b/Storage/Disk/DiskImage/Formats/AppleDSK.hpp index 2dabc6915..575643171 100644 --- a/Storage/Disk/DiskImage/Formats/AppleDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/AppleDSK.hpp @@ -20,28 +20,28 @@ namespace Storage::Disk { implicitly numbered and located. */ class AppleDSK: public DiskImage { - public: - /*! - Construct an @c AppleDSK containing content from the file with name @c file_name. +public: + /*! + Construct an @c AppleDSK containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an Apple DSK format image. - */ - AppleDSK(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an Apple DSK format image. + */ + AppleDSK(const std::string &file_name); - // Implemented to satisfy @c DiskImage. - HeadPosition get_maximum_head_position() final; - std::shared_ptr get_track_at_position(Track::Address address) final; - void set_tracks(const std::map> &tracks) final; - bool get_is_read_only() final; + // Implemented to satisfy @c DiskImage. + HeadPosition get_maximum_head_position() final; + std::shared_ptr get_track_at_position(Track::Address) final; + void set_tracks(const std::map> &) final; + bool get_is_read_only() final; - private: - Storage::FileHolder file_; - int sectors_per_track_ = 16; - bool is_prodos_ = false; +private: + Storage::FileHolder file_; + int sectors_per_track_ = 16; + bool is_prodos_ = false; - long file_offset(Track::Address address); - size_t logical_sector_for_physical_sector(size_t physical); + long file_offset(Track::Address); + size_t logical_sector_for_physical_sector(size_t physical); }; } diff --git a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp index d867f05a3..8a7386ee0 100644 --- a/Storage/Disk/DiskImage/Formats/CPCDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/CPCDSK.hpp @@ -21,51 +21,51 @@ namespace Storage::Disk { Provides a @c Disk containing an Amstrad CPC-type disk image: some arrangement of sectors with status bits. */ class CPCDSK: public DiskImage { - public: - /*! - Construct a @c CPCDSK containing content from the file with name @c file_name. +public: + /*! + Construct a @c CPCDSK containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image. - */ - CPCDSK(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image. + */ + CPCDSK(const std::string &file_name); - // DiskImage interface. - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - bool get_is_read_only() final; - void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + // DiskImage interface. + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + bool get_is_read_only() final; + void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - private: - struct Track { - uint8_t track; - uint8_t side; - enum class DataRate { - Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity - } data_rate; - enum class DataEncoding { - Unknown, FM, MFM - } data_encoding; - uint8_t sector_length; - uint8_t gap3_length; - uint8_t filler_byte; +private: + struct Track { + uint8_t track; + uint8_t side; + enum class DataRate { + Unknown, SingleOrDoubleDensity, HighDensity, ExtendedDensity + } data_rate; + enum class DataEncoding { + Unknown, FM, MFM + } data_encoding; + uint8_t sector_length; + uint8_t gap3_length; + uint8_t filler_byte; - struct Sector: public ::Storage::Encodings::MFM::Sector { - uint8_t fdc_status1; - uint8_t fdc_status2; - }; - - std::vector sectors; + struct Sector: public ::Storage::Encodings::MFM::Sector { + uint8_t fdc_status1; + uint8_t fdc_status2; }; - std::string file_name_; - std::vector> tracks_; - std::size_t index_for_track(::Storage::Disk::Track::Address address); - int head_count_; - int head_position_count_; - bool is_extended_; - bool is_read_only_; + std::vector sectors; + }; + std::string file_name_; + std::vector> tracks_; + std::size_t index_for_track(::Storage::Disk::Track::Address address); + + int head_count_; + int head_position_count_; + bool is_extended_; + bool is_read_only_; }; } diff --git a/Storage/Disk/DiskImage/Formats/D64.hpp b/Storage/Disk/DiskImage/Formats/D64.hpp index bed7273cb..ffe448024 100644 --- a/Storage/Disk/DiskImage/Formats/D64.hpp +++ b/Storage/Disk/DiskImage/Formats/D64.hpp @@ -17,24 +17,24 @@ namespace Storage::Disk { Provides a @c Disk containing a D64 disk image: a decoded sector dump of a C1540-format disk. */ class D64: public DiskImage { - public: - /*! - Construct a @c D64 containing content from the file with name @c file_name. +public: + /*! + Construct a @c D64 containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image. - */ - D64(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain a .D64 format image. + */ + D64(const std::string &file_name); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - using DiskImage::get_is_read_only; - std::shared_ptr get_track_at_position(Track::Address address) final; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + using DiskImage::get_is_read_only; + std::shared_ptr get_track_at_position(Track::Address address) final; - private: - Storage::FileHolder file_; - int number_of_tracks_; - uint16_t disk_id_; +private: + Storage::FileHolder file_; + int number_of_tracks_; + uint16_t disk_id_; }; } diff --git a/Storage/Disk/DiskImage/Formats/DMK.hpp b/Storage/Disk/DiskImage/Formats/DMK.hpp index dd2e29546..9629750ea 100644 --- a/Storage/Disk/DiskImage/Formats/DMK.hpp +++ b/Storage/Disk/DiskImage/Formats/DMK.hpp @@ -20,31 +20,31 @@ namespace Storage::Disk { a record of IDAM locations. */ class DMK: public DiskImage { - public: - /*! - Construct a @c DMK containing content from the file with name @c file_name. +public: + /*! + Construct a @c DMK containing content from the file with name @c file_name. - @throws Error::InvalidFormat if this file doesn't appear to be a DMK. - */ - DMK(const std::string &file_name); + @throws Error::InvalidFormat if this file doesn't appear to be a DMK. + */ + DMK(const std::string &file_name); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - bool get_is_read_only() final; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + bool get_is_read_only() final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - private: - FileHolder file_; - long get_file_offset_for_position(Track::Address address); +private: + FileHolder file_; + long get_file_offset_for_position(Track::Address address); - bool is_read_only_; - int head_position_count_; - int head_count_; + bool is_read_only_; + int head_position_count_; + int head_count_; - long track_length_; - bool is_purely_single_density_; + long track_length_; + bool is_purely_single_density_; }; } diff --git a/Storage/Disk/DiskImage/Formats/FAT12.hpp b/Storage/Disk/DiskImage/Formats/FAT12.hpp index 22683df94..c05cae7f9 100644 --- a/Storage/Disk/DiskImage/Formats/FAT12.hpp +++ b/Storage/Disk/DiskImage/Formats/FAT12.hpp @@ -19,18 +19,18 @@ namespace Storage::Disk { a sector dump of appropriate proportions. */ class FAT12: public MFMSectorDump { - public: - FAT12(const std::string &file_name); - HeadPosition get_maximum_head_position() final; - int get_head_count() final; +public: + FAT12(const std::string &file_name); + HeadPosition get_maximum_head_position() final; + int get_head_count() final; - private: - long get_file_offset_for_position(Track::Address address) final; +private: + long get_file_offset_for_position(Track::Address address) final; - int head_count_; - int track_count_; - int sector_count_; - int sector_size_; + int head_count_; + int track_count_; + int sector_count_; + int sector_size_; }; } diff --git a/Storage/Disk/DiskImage/Formats/G64.hpp b/Storage/Disk/DiskImage/Formats/G64.hpp index 942f42096..a50f50ba5 100644 --- a/Storage/Disk/DiskImage/Formats/G64.hpp +++ b/Storage/Disk/DiskImage/Formats/G64.hpp @@ -19,25 +19,25 @@ namespace Storage::Disk { Provides a @c Disk containing a G64 disk image: a raw but perfectly-clocked GCR stream. */ class G64: public DiskImage { - public: - /*! - Construct a @c G64 containing content from the file with name @c file_name. +public: + /*! + Construct a @c G64 containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain a .G64 format image. - @throws Error::UnknownVersion if this file appears to be a .G64 but has an unrecognised version number. - */ - G64(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain a .G64 format image. + @throws Error::UnknownVersion if this file appears to be a .G64 but has an unrecognised version number. + */ + G64(const std::string &file_name); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - std::shared_ptr get_track_at_position(Track::Address address) final; - using DiskImage::get_is_read_only; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + std::shared_ptr get_track_at_position(Track::Address address) final; + using DiskImage::get_is_read_only; - private: - Storage::FileHolder file_; - uint8_t number_of_tracks_; - uint16_t maximum_track_size_; +private: + Storage::FileHolder file_; + uint8_t number_of_tracks_; + uint16_t maximum_track_size_; }; } diff --git a/Storage/Disk/DiskImage/Formats/HFE.hpp b/Storage/Disk/DiskImage/Formats/HFE.hpp index 127f8685d..f57890d8e 100644 --- a/Storage/Disk/DiskImage/Formats/HFE.hpp +++ b/Storage/Disk/DiskImage/Formats/HFE.hpp @@ -19,30 +19,30 @@ namespace Storage::Disk { Provides a @c DiskImage containing an HFE: a bit stream representation of a floppy. */ class HFE: public DiskImage { - public: - /*! - Construct an @c HFE containing content from the file with name @c file_name. +public: + /*! + Construct an @c HFE containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an .HFE format image. - @throws Error::UnknownVersion if the file looks correct but is an unsupported version. - */ - HFE(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an .HFE format image. + @throws Error::UnknownVersion if the file looks correct but is an unsupported version. + */ + HFE(const std::string &file_name); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - bool get_is_read_only() final; - void set_tracks(const std::map> &tracks) final; - std::shared_ptr get_track_at_position(Track::Address address) final; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + bool get_is_read_only() final; + void set_tracks(const std::map> &tracks) final; + std::shared_ptr get_track_at_position(Track::Address address) final; - private: - Storage::FileHolder file_; - uint16_t seek_track(Track::Address address); +private: + Storage::FileHolder file_; + uint16_t seek_track(Track::Address address); - int head_count_; - int track_count_; - long track_list_offset_; + int head_count_; + int track_count_; + long track_list_offset_; }; } diff --git a/Storage/Disk/DiskImage/Formats/IMD.hpp b/Storage/Disk/DiskImage/Formats/IMD.hpp index 0614b9775..d91668ef2 100644 --- a/Storage/Disk/DiskImage/Formats/IMD.hpp +++ b/Storage/Disk/DiskImage/Formats/IMD.hpp @@ -19,24 +19,24 @@ namespace Storage::Disk { */ class IMD: public DiskImage { - public: - /*! - Construct an @c IMD containing content from the file with name @c file_name. +public: + /*! + Construct an @c IMD containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image. - */ - IMD(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image. + */ + IMD(const std::string &file_name); - // DiskImage interface. - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + // DiskImage interface. + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - private: - FileHolder file_; - std::map track_locations_; - uint8_t cylinders_ = 0, heads_ = 0; +private: + FileHolder file_; + std::map track_locations_; + uint8_t cylinders_ = 0, heads_ = 0; }; } diff --git a/Storage/Disk/DiskImage/Formats/IPF.hpp b/Storage/Disk/DiskImage/Formats/IPF.hpp index 597a8f8d3..d4a787502 100644 --- a/Storage/Disk/DiskImage/Formats/IPF.hpp +++ b/Storage/Disk/DiskImage/Formats/IPF.hpp @@ -25,62 +25,62 @@ namespace Storage::Disk { close in size to more primitive formats). */ class IPF: public DiskImage, public TargetPlatform::TypeDistinguisher { - public: - /*! - Construct an @c IPF containing content from the file with name @c file_name. +public: + /*! + Construct an @c IPF containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain an .HFE format image. - @throws Error::UnknownVersion if the file looks correct but is an unsupported version. - */ - IPF(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain an .HFE format image. + @throws Error::UnknownVersion if the file looks correct but is an unsupported version. + */ + IPF(const std::string &file_name); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - std::shared_ptr get_track_at_position(Track::Address address) final; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + std::shared_ptr get_track_at_position(Track::Address address) final; - private: - Storage::FileHolder file_; - uint16_t seek_track(Track::Address address); +private: + Storage::FileHolder file_; + uint16_t seek_track(Track::Address address); - struct TrackDescription { - long file_offset = 0; - enum class Density { - Unknown, - Noise, - Auto, - CopylockAmiga, - CopylockAmigaNew, - CopylockST, - SpeedlockAmiga, - OldSpeedlockAmiga, - AdamBrierleyAmiga, - AdamBrierleyDensityKeyAmiga, + struct TrackDescription { + long file_offset = 0; + enum class Density { + Unknown, + Noise, + Auto, + CopylockAmiga, + CopylockAmigaNew, + CopylockST, + SpeedlockAmiga, + OldSpeedlockAmiga, + AdamBrierleyAmiga, + AdamBrierleyDensityKeyAmiga, - Max = AdamBrierleyDensityKeyAmiga - } density = Density::Unknown; - uint32_t start_bit_pos = 0; - uint32_t data_bits = 0; - uint32_t gap_bits = 0; - uint32_t block_count; - bool has_fuzzy_bits = false; - }; + Max = AdamBrierleyDensityKeyAmiga + } density = Density::Unknown; + uint32_t start_bit_pos = 0; + uint32_t data_bits = 0; + uint32_t gap_bits = 0; + uint32_t block_count; + bool has_fuzzy_bits = false; + }; - int head_count_; - int track_count_; - std::map tracks_; - bool is_sps_format_ = false; + int head_count_; + int track_count_; + std::map tracks_; + bool is_sps_format_ = false; - TargetPlatform::Type target_platform_type() final { - return TargetPlatform::Type(platform_type_); - } - TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga; + TargetPlatform::Type target_platform_type() final { + return TargetPlatform::Type(platform_type_); + } + TargetPlatform::IntType platform_type_ = TargetPlatform::Amiga; - Time bit_length(TrackDescription::Density, int block); - void add_gap(std::vector &, Time bit_length, size_t num_bits, uint32_t value); - void add_unencoded_data(std::vector &, Time bit_length, size_t num_bits); - void add_raw_data(std::vector &, Time bit_length, size_t num_bits); + Time bit_length(TrackDescription::Density, int block); + void add_gap(std::vector &, Time bit_length, size_t num_bits, uint32_t value); + void add_unencoded_data(std::vector &, Time bit_length, size_t num_bits); + void add_raw_data(std::vector &, Time bit_length, size_t num_bits); }; } diff --git a/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp b/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp index c58a5b237..33d7a354a 100644 --- a/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp +++ b/Storage/Disk/DiskImage/Formats/MFMSectorDump.hpp @@ -20,24 +20,24 @@ namespace Storage::Disk { Provides the base for writeable [M]FM disk images that just contain contiguous sector content dumps. */ class MFMSectorDump: public DiskImage { - public: - MFMSectorDump(const std::string &file_name); +public: + MFMSectorDump(const std::string &file_name); - bool get_is_read_only() final; - void set_tracks(const std::map> &tracks) final; - std::shared_ptr get_track_at_position(Track::Address address) final; + bool get_is_read_only() final; + void set_tracks(const std::map> &tracks) final; + std::shared_ptr get_track_at_position(Track::Address address) final; - protected: - Storage::FileHolder file_; - void set_geometry(int sectors_per_track, uint8_t sector_size, uint8_t first_sector, Encodings::MFM::Density density); +protected: + Storage::FileHolder file_; + void set_geometry(int sectors_per_track, uint8_t sector_size, uint8_t first_sector, Encodings::MFM::Density density); - private: - virtual long get_file_offset_for_position(Track::Address address) = 0; +private: + virtual long get_file_offset_for_position(Track::Address address) = 0; - int sectors_per_track_ = 0; - uint8_t sector_size_ = 0; - Encodings::MFM::Density density_ = Encodings::MFM::Density::Single; - uint8_t first_sector_ = 0; + int sectors_per_track_ = 0; + uint8_t sector_size_ = 0; + Encodings::MFM::Density density_ = Encodings::MFM::Density::Single; + uint8_t first_sector_ = 0; }; } diff --git a/Storage/Disk/DiskImage/Formats/MSA.hpp b/Storage/Disk/DiskImage/Formats/MSA.hpp index c2e404f6b..ac067efcf 100644 --- a/Storage/Disk/DiskImage/Formats/MSA.hpp +++ b/Storage/Disk/DiskImage/Formats/MSA.hpp @@ -20,23 +20,23 @@ namespace Storage::Disk { a track dump with some metadata and potentially patches of RLE compression. */ class MSA final: public DiskImage { - public: - MSA(const std::string &file_name); +public: + MSA(const std::string &file_name); - // Implemented to satisfy @c DiskImage. - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - bool get_is_read_only() final { return false; } + // Implemented to satisfy @c DiskImage. + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + bool get_is_read_only() final { return false; } - private: - FileHolder file_; - uint16_t sectors_per_track_; - uint16_t sides_; - uint16_t starting_track_; - uint16_t ending_track_; +private: + FileHolder file_; + uint16_t sectors_per_track_; + uint16_t sides_; + uint16_t starting_track_; + uint16_t ending_track_; - std::vector> uncompressed_tracks_; + std::vector> uncompressed_tracks_; }; } diff --git a/Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp b/Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp index 1342f73ac..a24acadc0 100644 --- a/Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp +++ b/Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp @@ -21,54 +21,54 @@ namespace Storage::Disk { * a raw sector dump of a Macintosh GCR disk. */ class MacintoshIMG: public DiskImage { - public: - /*! - Construct a @c MacintoshIMG containing content from the file with name @c file_name. +public: + /*! + Construct a @c MacintoshIMG containing content from the file with name @c file_name. - @throws Error::InvalidFormat if this file doesn't appear to be in Disk Copy 4.2 format. - */ - MacintoshIMG(const std::string &file_name); + @throws Error::InvalidFormat if this file doesn't appear to be in Disk Copy 4.2 format. + */ + MacintoshIMG(const std::string &file_name); - enum class FixedType { - GCR - }; - /*! - Constructs a @c MacintoshIMG without attempting to autodetect whether this is a raw - image or a Disk Copy 4.2 image; if GCR is specified and the file size checks out then - it is accepted as a GCR image. + enum class FixedType { + GCR + }; + /*! + Constructs a @c MacintoshIMG without attempting to autodetect whether this is a raw + image or a Disk Copy 4.2 image; if GCR is specified and the file size checks out then + it is accepted as a GCR image. - If @c offset and @c length are specified and non-zero, only that portion of the file - will be modified. - */ - MacintoshIMG(const std::string &file_name, FixedType type, size_t offset = 0, size_t length = 0); + If @c offset and @c length are specified and non-zero, only that portion of the file + will be modified. + */ + MacintoshIMG(const std::string &file_name, FixedType type, size_t offset = 0, size_t length = 0); - // implemented to satisfy @c Disk - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - bool get_is_read_only() final; + // implemented to satisfy @c Disk + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + bool get_is_read_only() final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - void set_tracks(const std::map> &tracks) final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + void set_tracks(const std::map> &tracks) final; - private: - Storage::FileHolder file_; +private: + Storage::FileHolder file_; - enum class Encoding { - GCR400, - GCR800, - MFM720, - MFM1440 - } encoding_; - uint8_t format_; + enum class Encoding { + GCR400, + GCR800, + MFM720, + MFM1440 + } encoding_; + uint8_t format_; - std::vector data_; - std::vector tags_; - bool is_diskCopy_file_ = false; - std::mutex buffer_mutex_; + std::vector data_; + std::vector tags_; + bool is_diskCopy_file_ = false; + std::mutex buffer_mutex_; - uint32_t checksum(const std::vector &, size_t bytes_to_skip = 0); - void construct_raw_gcr(size_t offset, size_t length = 0); - long raw_offset_ = 0; + uint32_t checksum(const std::vector &, size_t bytes_to_skip = 0); + void construct_raw_gcr(size_t offset, size_t length = 0); + long raw_offset_ = 0; }; } diff --git a/Storage/Disk/DiskImage/Formats/NIB.hpp b/Storage/Disk/DiskImage/Formats/NIB.hpp index b4ff53b81..500f3d353 100644 --- a/Storage/Disk/DiskImage/Formats/NIB.hpp +++ b/Storage/Disk/DiskImage/Formats/NIB.hpp @@ -22,25 +22,25 @@ namespace Storage::Disk { the means for full reconstruction. */ class NIB: public DiskImage { - public: - NIB(const std::string &file_name); +public: + NIB(const std::string &file_name); - // Implemented to satisfy @c DiskImage. - HeadPosition get_maximum_head_position() final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - void set_tracks(const std::map> &tracks) final; - bool get_is_read_only() final; + // Implemented to satisfy @c DiskImage. + HeadPosition get_maximum_head_position() final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + void set_tracks(const std::map> &tracks) final; + bool get_is_read_only() final; - private: - FileHolder file_; - long get_file_offset_for_position(Track::Address address); - long file_offset(Track::Address address); +private: + FileHolder file_; + long get_file_offset_for_position(Track::Address address); + long file_offset(Track::Address address); - // Cache for the last-generated track, given that head steps on an Apple II - // occur in quarter-track increments, so there'll routinely be four gets in - // a row for the same data. - long cached_offset_ = 0; - std::shared_ptr cached_track_; + // Cache for the last-generated track, given that head steps on an Apple II + // occur in quarter-track increments, so there'll routinely be four gets in + // a row for the same data. + long cached_offset_ = 0; + std::shared_ptr cached_track_; }; } diff --git a/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp b/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp index 51b37c2aa..0c38254d4 100644 --- a/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp +++ b/Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp @@ -19,29 +19,29 @@ namespace Storage::Disk { Provides a @c Disk containing an Oric MFM-stype disk image: a stream of the MFM data bits with clocks omitted. */ class OricMFMDSK: public DiskImage { - public: - /*! - Construct an @c OricMFMDSK containing content from the file with name @c file_name. +public: + /*! + Construct an @c OricMFMDSK containing content from the file with name @c file_name. - @throws ErrorNotOricMFMDSK if the file doesn't appear to contain an Oric MFM format image. - */ - OricMFMDSK(const std::string &file_name); + @throws ErrorNotOricMFMDSK if the file doesn't appear to contain an Oric MFM format image. + */ + OricMFMDSK(const std::string &file_name); - // implemented to satisfy @c DiskImage - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - bool get_is_read_only() final; + // implemented to satisfy @c DiskImage + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + bool get_is_read_only() final; - void set_tracks(const std::map> &tracks) final; - std::shared_ptr get_track_at_position(Track::Address address) final; + void set_tracks(const std::map> &tracks) final; + std::shared_ptr get_track_at_position(Track::Address address) final; - private: - Storage::FileHolder file_; - long get_file_offset_for_position(Track::Address address); +private: + Storage::FileHolder file_; + long get_file_offset_for_position(Track::Address address); - uint32_t head_count_; - uint32_t track_count_; - uint32_t geometry_type_; + uint32_t head_count_; + uint32_t track_count_; + uint32_t geometry_type_; }; } diff --git a/Storage/Disk/DiskImage/Formats/PCBooter.hpp b/Storage/Disk/DiskImage/Formats/PCBooter.hpp index d99dae690..ced706328 100644 --- a/Storage/Disk/DiskImage/Formats/PCBooter.hpp +++ b/Storage/Disk/DiskImage/Formats/PCBooter.hpp @@ -19,17 +19,17 @@ namespace Storage::Disk { with what looks like a meaningful boot sector. */ class PCBooter: public MFMSectorDump { - public: - PCBooter(const std::string &file_name); - HeadPosition get_maximum_head_position() final; - int get_head_count() final; +public: + PCBooter(const std::string &file_name); + HeadPosition get_maximum_head_position() final; + int get_head_count() final; - private: - long get_file_offset_for_position(Track::Address address) final; +private: + long get_file_offset_for_position(Track::Address address) final; - int head_count_; - int track_count_; - int sector_count_; + int head_count_; + int track_count_; + int sector_count_; }; } diff --git a/Storage/Disk/DiskImage/Formats/SSD.hpp b/Storage/Disk/DiskImage/Formats/SSD.hpp index 696dd59d9..ba784d475 100644 --- a/Storage/Disk/DiskImage/Formats/SSD.hpp +++ b/Storage/Disk/DiskImage/Formats/SSD.hpp @@ -16,23 +16,23 @@ namespace Storage::Disk { Provides a @c Disk containing a DSD or SSD disk image: a decoded sector dump of an Acorn DFS disk. */ class SSD: public MFMSectorDump { - public: - /*! - Construct an @c SSD containing content from the file with name @c file_name. +public: + /*! + Construct an @c SSD containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain a .SSD format image. - */ - SSD(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain a .SSD format image. + */ + SSD(const std::string &file_name); - HeadPosition get_maximum_head_position() final; - int get_head_count() final; + HeadPosition get_maximum_head_position() final; + int get_head_count() final; - private: - long get_file_offset_for_position(Track::Address address) final; +private: + long get_file_offset_for_position(Track::Address address) final; - int head_count_; - int track_count_; + int head_count_; + int track_count_; }; } diff --git a/Storage/Disk/DiskImage/Formats/STX.cpp b/Storage/Disk/DiskImage/Formats/STX.cpp index 4c26038ed..c3884dffd 100644 --- a/Storage/Disk/DiskImage/Formats/STX.cpp +++ b/Storage/Disk/DiskImage/Formats/STX.cpp @@ -24,367 +24,367 @@ using namespace Storage::Disk; namespace { class TrackConstructor { - public: - constexpr static uint16_t NoFirstOffset = std::numeric_limits::max(); +public: + constexpr static uint16_t NoFirstOffset = std::numeric_limits::max(); - struct Sector { - // Records explicitly present in the sector table. - uint32_t data_offset = 0; - size_t bit_position = 0; - uint16_t data_duration = 0; - std::array address = {0, 0, 0, 0, 0, 0}; - uint8_t status = 0; + struct Sector { + // Records explicitly present in the sector table. + uint32_t data_offset = 0; + size_t bit_position = 0; + uint16_t data_duration = 0; + std::array address = {0, 0, 0, 0, 0, 0}; + uint8_t status = 0; - // Other facts that will either be supplied by the STX or which - // will be empty. - std::vector fuzzy_mask; - std::vector contents; - std::vector timing; + // Other facts that will either be supplied by the STX or which + // will be empty. + std::vector fuzzy_mask; + std::vector contents; + std::vector timing; - // Accessors. + // Accessors. - /// @returns The byte size of this sector, according to its address mark. - uint32_t data_size() const { - return uint32_t(128 << (address[3]&3)); - } - - struct Fragment { - int prior_syncs = 1; - std::vector contents; - }; - - /// @returns The byte stream this sector address would produce if a WD read track command were to observe it. - std::vector get_track_address_fragments() const { - return track_fragments(address.begin(), address.begin() + 4, {0xa1, 0xa1, 0xfe}); - } - - /// @returns The byte stream this sector data would produce if a WD read track command were to observe it. - std::vector get_track_data_fragments() const { - return track_fragments(contents.begin(), contents.end(), {0xa1, 0xa1, 0xfb}); - } - - /*! - Acts like std::search except that it tries to find a start location from which all of the members of @c fragments - can be found in successive order with no more than a 'permissible' amount of gap between them. - - Where 'permissible' is derived empirically from trial and error; in practice it's a measure of the number of bytes - a WD may produce when it has encountered a false sync, and I don't have documentation on that. So it's - derived from in-practice testing of STXs (which, hopefully, contain an accurate copy of what a WD would do, - so are themselves possibly a way to research that). - */ - template static Iterator find_fragments(Iterator begin, Iterator end, const std::vector &fragments) { - while(true) { - // To match the fragments, they must all be found, in order, with at most two bytes of gap. - auto this_begin = begin; - std::vector::const_iterator first_location = end; - bool is_found = true; - bool is_first = true; - for(auto fragment: fragments) { - auto location = std::search(this_begin, end, fragment.contents.begin(), fragment.contents.end()); - - // If fragment wasn't found at all, it's never going to be found. So game over. - if(location == end) { - return location; - } - - // Otherwise, either mark - if(is_first) { - first_location = location; - } else if(location > this_begin + 5*fragment.prior_syncs) { - is_found = false; - break; - } - - is_first = false; - this_begin = location + ssize_t(fragment.contents.size()); - } - - if(is_found) { - return first_location; - } - - // TODO: can I assume more than this? - ++begin; - } - return end; - } - - private: - /// @returns The effect of encoding @c prefix followed by the bytes from @c begin to @c end as MFM data and then decoding them as if - /// observed by a WD read track command, split into fragments separated by any instances of false sync — since it's still unclear to me exactly what - /// a WD should put out in those instances. - template static std::vector track_fragments(T begin, T end, std::initializer_list prefix) { - std::vector result; - result.reserve(size_t(end - begin) + prefix.size()); - - PCMSegment segment; - std::unique_ptr encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); - - // Encode prefix. - for(auto c: prefix) { - encoder->add_byte(c); - } - - // Encode body. - while(begin != end) { - encoder->add_byte(*begin); - ++begin; - } - - // Decode, starting a new segment upon any false sync since I don't have good documentation - // presently on exactly how a WD should react to those. - using Shifter = Storage::Encodings::MFM::Shifter; - Shifter shifter; - shifter.set_should_obey_syncs(true); - shifter.set_is_mfm(true); - - result.emplace_back(); - - // Add whatever comes from the track. - int ignore_count = 0; - for(auto bit: segment.data) { - shifter.add_input_bit(int(bit)); - - const auto token = shifter.get_token(); - if(token != Shifter::None) { - if(ignore_count) { - --ignore_count; - continue; - } - - // If anything other than a byte is encountered, - // skip it and the next thing to be reported, - // beginning a new fragment. - if(token != Shifter::Token::Byte) { - ignore_count = 1; - - if(!result.back().contents.empty()) { - result.emplace_back(); - } else { - ++result.back().prior_syncs; - } - - continue; - } - - // This was an ordinary byte, retain it. - result.back().contents.push_back(shifter.get_byte()); - } - } - - return result; - } - }; - - - TrackConstructor(const std::vector &track_data, const std::vector §ors, size_t track_size, uint16_t first_sync) : - track_data_(track_data), sectors_(sectors), track_size_(track_size), first_sync_(first_sync) { - (void)first_sync_; + /// @returns The byte size of this sector, according to its address mark. + uint32_t data_size() const { + return uint32_t(128 << (address[3]&3)); } - std::shared_ptr get_track() { - // If no contents are supplied, return an unformatted track. - if(sectors_.empty() && track_data_.empty()) { - return nullptr; - } + struct Fragment { + int prior_syncs = 1; + std::vector contents; + }; - // If no sectors are on this track, just encode the track data. STX allows speed - // changes and fuzzy bits in sectors only. - if(sectors_.empty()) { - PCMSegment segment; - std::unique_ptr encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); - for(auto c: track_data_) { - encoder->add_byte(c); - } - return std::make_shared(segment); - } + /// @returns The byte stream this sector address would produce if a WD read track command were to observe it. + std::vector get_track_address_fragments() const { + return track_fragments(address.begin(), address.begin() + 4, {0xa1, 0xa1, 0xfe}); + } - // Otherwise, seek to encode the sectors, using the track data to - // fill in the gaps (if provided). - std::unique_ptr encoder; - std::vector segments; + /// @returns The byte stream this sector data would produce if a WD read track command were to observe it. + std::vector get_track_data_fragments() const { + return track_fragments(contents.begin(), contents.end(), {0xa1, 0xa1, 0xfb}); + } - // To reconcile the list of sectors with the WD get track-style track image, - // use sector bodies as definitive and refer to the track image for in-fill. - auto track_position = track_data_.begin(); - const auto sync_mark = {0xa1, 0xa1}; - struct Location { - enum Type { - Address, Data - } type; - std::vector::const_iterator position; - const Sector §or; + /*! + Acts like std::search except that it tries to find a start location from which all of the members of @c fragments + can be found in successive order with no more than a 'permissible' amount of gap between them. - Location(Type type, std::vector::const_iterator position, const Sector §or) : type(type), position(position), sector(sector) {} - }; - std::vector locations; - for(const auto §or: sectors_) { - { - // Find out what the address would look like, if found in a read track. - const auto address_fragments = sector.get_track_address_fragments(); + Where 'permissible' is derived empirically from trial and error; in practice it's a measure of the number of bytes + a WD may produce when it has encountered a false sync, and I don't have documentation on that. So it's + derived from in-practice testing of STXs (which, hopefully, contain an accurate copy of what a WD would do, + so are themselves possibly a way to research that). + */ + template static Iterator find_fragments(Iterator begin, Iterator end, const std::vector &fragments) { + while(true) { + // To match the fragments, they must all be found, in order, with at most two bytes of gap. + auto this_begin = begin; + std::vector::const_iterator first_location = end; + bool is_found = true; + bool is_first = true; + for(auto fragment: fragments) { + auto location = std::search(this_begin, end, fragment.contents.begin(), fragment.contents.end()); - // Try to locate the header within the track image; if it can't be found then settle for - // the next thing that looks like a header of any sort. - auto address_position = TrackConstructor::Sector::find_fragments(track_position, track_data_.end(), address_fragments); - if(address_position == track_data_.end()) { - address_position = std::search(track_position, track_data_.end(), sync_mark.begin(), sync_mark.end()); + // If fragment wasn't found at all, it's never going to be found. So game over. + if(location == end) { + return location; } - // Place this address only if somewhere to put it was found. - if(address_position != track_data_.end()) { - locations.emplace_back(Location::Address, address_position, sector); - - // Advance the track position. - track_position = address_position + 6; - } - } - - // Do much the same thing for the data, if it exists. - if(!(sector.status & 0x10)) { - const auto data_fragments = sector.get_track_data_fragments(); - - auto data_position = TrackConstructor::Sector::find_fragments(track_position, track_data_.end(), data_fragments); - if(data_position == track_data_.end()) { - data_position = std::search(track_position, track_data_.end(), sync_mark.begin(), sync_mark.end()); - } - if(data_position == track_data_.end()) { - // Desperation: guess from the given offset. - data_position = track_data_.begin() + (sector.bit_position / 16); + // Otherwise, either mark + if(is_first) { + first_location = location; + } else if(location > this_begin + 5*fragment.prior_syncs) { + is_found = false; + break; } - locations.emplace_back(Location::Data, data_position, sector); - track_position = data_position + sector.data_size(); - } - } - - const auto encoder_at_rate = [&encoder, &segments](unsigned int rate) -> Storage::Encodings::MFM::Encoder* { - if(!encoder) { - segments.emplace_back(); - segments.back().length_of_a_bit = Storage::Time(int(rate + 1), 1); - encoder = Storage::Encodings::MFM::GetMFMEncoder(segments.back().data, &segments.back().fuzzy_mask); - } else if(segments.back().length_of_a_bit.length != rate) { - segments.emplace_back(); - segments.back().length_of_a_bit = Storage::Time(int(rate + 1), 1); - encoder->reset_target(segments.back().data, &segments.back().fuzzy_mask); - } - return encoder.get(); - }; - - // Write out, being wary of potential overlapping sectors, and copying from track_data_ to fill in gaps. - auto location = locations.begin(); - track_position = track_data_.begin(); - while(location != locations.end()) { -// assert(location->position >= track_position && location->position < track_data_.end()); - - // Advance to location.position. - auto default_rate_encoder = encoder_at_rate(127); - while(track_position < location->position) { - default_rate_encoder->add_byte(*track_position); - ++track_position; + is_first = false; + this_begin = location + ssize_t(fragment.contents.size()); } - // Write the relevant mark and fill in a default number of bytes to write. - size_t bytes_to_write; - switch(location->type) { - default: - case Location::Address: - default_rate_encoder->add_ID_address_mark(); - bytes_to_write = 6; - break; - case Location::Data: - if(location->sector.status & 0x20) - default_rate_encoder->add_deleted_data_address_mark(); - else - default_rate_encoder->add_data_address_mark(); - bytes_to_write = location->sector.data_size() + 2; - break; - } - track_position += 3; - - // Decide how much data to write for real; this [partially] allows for overlapping sectors. - auto next_location = location + 1; - if(next_location != locations.end()) { - bytes_to_write = std::min(bytes_to_write, size_t(next_location->position - track_position)); + if(is_found) { + return first_location; } - // Skip that many bytes from the underlying track image. - track_position += ssize_t(bytes_to_write); - - // Write bytes. - switch(location->type) { - default: - case Location::Address: - for(size_t c = 0; c < bytes_to_write; ++c) - default_rate_encoder->add_byte(location->sector.address[c]); - break; - case Location::Data: { - const auto body_bytes = std::min(bytes_to_write, size_t(location->sector.data_size())); - - // If timing information is attached to this sector, write each byte at the proper speed. - // (TODO: is there any benefit to optiming number of calls to encoder_at_rate?) - if(!location->sector.timing.empty()) { - for(size_t c = 0; c < body_bytes; ++c) { - encoder_at_rate(location->sector.timing[c >> 4])->add_byte( - location->sector.contents[c], - location->sector.fuzzy_mask.empty() ? 0x00 : location->sector.fuzzy_mask[c] - ); - } - } else { - for(size_t c = 0; c < body_bytes; ++c) { - default_rate_encoder->add_byte( - location->sector.contents[c], - location->sector.fuzzy_mask.empty() ? 0x00 : location->sector.fuzzy_mask[c] - ); - } - } - - // Add a CRC only if it fits (TODO: crop if necessary?). - if(bytes_to_write & 127) { - default_rate_encoder = encoder_at_rate(127); - default_rate_encoder->add_crc((location->sector.status & 0x18) == 0x10); - } - } break; - } - - // Advance location. - ++location; + // TODO: can I assume more than this? + ++begin; } - - // Write anything remaining from the track image. - while(track_position < track_data_.end()) { - encoder->add_byte(*track_position); - ++track_position; - } - - // Count total size of track. - size_t track_size = 0; - for(auto &segment: segments) { - track_size += segment.data.size(); - } - - // Write generic padding up until the specified track size. - while(track_size < track_size_ * 16) { - encoder->add_byte(0x4e); - track_size += 16; - } - - // Pad out to the minimum size a WD can actually make sense of. - // I've no idea why it's valid for tracks to be shorter than this, - // so likely I'm suffering a comprehansion deficiency. - // TODO: determine why this isn't correct (or, possibly, is). - while(track_size < 5750 * 16) { - encoder->add_byte(0x4e); - track_size += 16; - } - - return std::make_shared(segments); + return end; } private: - const std::vector &track_data_; - const std::vector §ors_; - const size_t track_size_; - const uint16_t first_sync_; + /// @returns The effect of encoding @c prefix followed by the bytes from @c begin to @c end as MFM data and then decoding them as if + /// observed by a WD read track command, split into fragments separated by any instances of false sync — since it's still unclear to me exactly what + /// a WD should put out in those instances. + template static std::vector track_fragments(T begin, T end, std::initializer_list prefix) { + std::vector result; + result.reserve(size_t(end - begin) + prefix.size()); + + PCMSegment segment; + std::unique_ptr encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + + // Encode prefix. + for(auto c: prefix) { + encoder->add_byte(c); + } + + // Encode body. + while(begin != end) { + encoder->add_byte(*begin); + ++begin; + } + + // Decode, starting a new segment upon any false sync since I don't have good documentation + // presently on exactly how a WD should react to those. + using Shifter = Storage::Encodings::MFM::Shifter; + Shifter shifter; + shifter.set_should_obey_syncs(true); + shifter.set_is_mfm(true); + + result.emplace_back(); + + // Add whatever comes from the track. + int ignore_count = 0; + for(auto bit: segment.data) { + shifter.add_input_bit(int(bit)); + + const auto token = shifter.get_token(); + if(token != Shifter::None) { + if(ignore_count) { + --ignore_count; + continue; + } + + // If anything other than a byte is encountered, + // skip it and the next thing to be reported, + // beginning a new fragment. + if(token != Shifter::Token::Byte) { + ignore_count = 1; + + if(!result.back().contents.empty()) { + result.emplace_back(); + } else { + ++result.back().prior_syncs; + } + + continue; + } + + // This was an ordinary byte, retain it. + result.back().contents.push_back(shifter.get_byte()); + } + } + + return result; + } + }; + + + TrackConstructor(const std::vector &track_data, const std::vector §ors, size_t track_size, uint16_t first_sync) : + track_data_(track_data), sectors_(sectors), track_size_(track_size), first_sync_(first_sync) { + (void)first_sync_; + } + + std::shared_ptr get_track() { + // If no contents are supplied, return an unformatted track. + if(sectors_.empty() && track_data_.empty()) { + return nullptr; + } + + // If no sectors are on this track, just encode the track data. STX allows speed + // changes and fuzzy bits in sectors only. + if(sectors_.empty()) { + PCMSegment segment; + std::unique_ptr encoder = Storage::Encodings::MFM::GetMFMEncoder(segment.data); + for(auto c: track_data_) { + encoder->add_byte(c); + } + return std::make_shared(segment); + } + + // Otherwise, seek to encode the sectors, using the track data to + // fill in the gaps (if provided). + std::unique_ptr encoder; + std::vector segments; + + // To reconcile the list of sectors with the WD get track-style track image, + // use sector bodies as definitive and refer to the track image for in-fill. + auto track_position = track_data_.begin(); + const auto sync_mark = {0xa1, 0xa1}; + struct Location { + enum Type { + Address, Data + } type; + std::vector::const_iterator position; + const Sector §or; + + Location(Type type, std::vector::const_iterator position, const Sector §or) : type(type), position(position), sector(sector) {} + }; + std::vector locations; + for(const auto §or: sectors_) { + { + // Find out what the address would look like, if found in a read track. + const auto address_fragments = sector.get_track_address_fragments(); + + // Try to locate the header within the track image; if it can't be found then settle for + // the next thing that looks like a header of any sort. + auto address_position = TrackConstructor::Sector::find_fragments(track_position, track_data_.end(), address_fragments); + if(address_position == track_data_.end()) { + address_position = std::search(track_position, track_data_.end(), sync_mark.begin(), sync_mark.end()); + } + + // Place this address only if somewhere to put it was found. + if(address_position != track_data_.end()) { + locations.emplace_back(Location::Address, address_position, sector); + + // Advance the track position. + track_position = address_position + 6; + } + } + + // Do much the same thing for the data, if it exists. + if(!(sector.status & 0x10)) { + const auto data_fragments = sector.get_track_data_fragments(); + + auto data_position = TrackConstructor::Sector::find_fragments(track_position, track_data_.end(), data_fragments); + if(data_position == track_data_.end()) { + data_position = std::search(track_position, track_data_.end(), sync_mark.begin(), sync_mark.end()); + } + if(data_position == track_data_.end()) { + // Desperation: guess from the given offset. + data_position = track_data_.begin() + (sector.bit_position / 16); + } + + locations.emplace_back(Location::Data, data_position, sector); + track_position = data_position + sector.data_size(); + } + } + + const auto encoder_at_rate = [&encoder, &segments](unsigned int rate) -> Storage::Encodings::MFM::Encoder* { + if(!encoder) { + segments.emplace_back(); + segments.back().length_of_a_bit = Storage::Time(int(rate + 1), 1); + encoder = Storage::Encodings::MFM::GetMFMEncoder(segments.back().data, &segments.back().fuzzy_mask); + } else if(segments.back().length_of_a_bit.length != rate) { + segments.emplace_back(); + segments.back().length_of_a_bit = Storage::Time(int(rate + 1), 1); + encoder->reset_target(segments.back().data, &segments.back().fuzzy_mask); + } + return encoder.get(); + }; + + // Write out, being wary of potential overlapping sectors, and copying from track_data_ to fill in gaps. + auto location = locations.begin(); + track_position = track_data_.begin(); + while(location != locations.end()) { +// assert(location->position >= track_position && location->position < track_data_.end()); + + // Advance to location.position. + auto default_rate_encoder = encoder_at_rate(127); + while(track_position < location->position) { + default_rate_encoder->add_byte(*track_position); + ++track_position; + } + + // Write the relevant mark and fill in a default number of bytes to write. + size_t bytes_to_write; + switch(location->type) { + default: + case Location::Address: + default_rate_encoder->add_ID_address_mark(); + bytes_to_write = 6; + break; + case Location::Data: + if(location->sector.status & 0x20) + default_rate_encoder->add_deleted_data_address_mark(); + else + default_rate_encoder->add_data_address_mark(); + bytes_to_write = location->sector.data_size() + 2; + break; + } + track_position += 3; + + // Decide how much data to write for real; this [partially] allows for overlapping sectors. + auto next_location = location + 1; + if(next_location != locations.end()) { + bytes_to_write = std::min(bytes_to_write, size_t(next_location->position - track_position)); + } + + // Skip that many bytes from the underlying track image. + track_position += ssize_t(bytes_to_write); + + // Write bytes. + switch(location->type) { + default: + case Location::Address: + for(size_t c = 0; c < bytes_to_write; ++c) + default_rate_encoder->add_byte(location->sector.address[c]); + break; + case Location::Data: { + const auto body_bytes = std::min(bytes_to_write, size_t(location->sector.data_size())); + + // If timing information is attached to this sector, write each byte at the proper speed. + // (TODO: is there any benefit to optiming number of calls to encoder_at_rate?) + if(!location->sector.timing.empty()) { + for(size_t c = 0; c < body_bytes; ++c) { + encoder_at_rate(location->sector.timing[c >> 4])->add_byte( + location->sector.contents[c], + location->sector.fuzzy_mask.empty() ? 0x00 : location->sector.fuzzy_mask[c] + ); + } + } else { + for(size_t c = 0; c < body_bytes; ++c) { + default_rate_encoder->add_byte( + location->sector.contents[c], + location->sector.fuzzy_mask.empty() ? 0x00 : location->sector.fuzzy_mask[c] + ); + } + } + + // Add a CRC only if it fits (TODO: crop if necessary?). + if(bytes_to_write & 127) { + default_rate_encoder = encoder_at_rate(127); + default_rate_encoder->add_crc((location->sector.status & 0x18) == 0x10); + } + } break; + } + + // Advance location. + ++location; + } + + // Write anything remaining from the track image. + while(track_position < track_data_.end()) { + encoder->add_byte(*track_position); + ++track_position; + } + + // Count total size of track. + size_t track_size = 0; + for(auto &segment: segments) { + track_size += segment.data.size(); + } + + // Write generic padding up until the specified track size. + while(track_size < track_size_ * 16) { + encoder->add_byte(0x4e); + track_size += 16; + } + + // Pad out to the minimum size a WD can actually make sense of. + // I've no idea why it's valid for tracks to be shorter than this, + // so likely I'm suffering a comprehansion deficiency. + // TODO: determine why this isn't correct (or, possibly, is). + while(track_size < 5750 * 16) { + encoder->add_byte(0x4e); + track_size += 16; + } + + return std::make_shared(segments); + } + +private: + const std::vector &track_data_; + const std::vector §ors_; + const size_t track_size_; + const uint16_t first_sync_; }; } diff --git a/Storage/Disk/DiskImage/Formats/STX.hpp b/Storage/Disk/DiskImage/Formats/STX.hpp index 08854949e..d40d1bd8a 100644 --- a/Storage/Disk/DiskImage/Formats/STX.hpp +++ b/Storage/Disk/DiskImage/Formats/STX.hpp @@ -18,28 +18,28 @@ namespace Storage::Disk { placement, bit density, fuzzy bits, etc. */ class STX: public DiskImage { - public: - /*! - Construct an @c STX containing content from the file with name @c file_name. +public: + /*! + Construct an @c STX containing content from the file with name @c file_name. - @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. - @throws Error::InvalidFormat if the file doesn't appear to contain a .STX format image. - */ - STX(const std::string &file_name); + @throws Storage::FileHolder::Error::CantOpen if this file can't be opened. + @throws Error::InvalidFormat if the file doesn't appear to contain a .STX format image. + */ + STX(const std::string &file_name); - HeadPosition get_maximum_head_position() final; - int get_head_count() final; + HeadPosition get_maximum_head_position() final; + int get_head_count() final; - std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final; - private: - FileHolder file_; +private: + FileHolder file_; - int track_count_; - int head_count_; + int track_count_; + int head_count_; - bool is_new_format_; - long offset_by_track_[256]; + bool is_new_format_; + long offset_by_track_[256]; }; } diff --git a/Storage/Disk/DiskImage/Formats/WOZ.hpp b/Storage/Disk/DiskImage/Formats/WOZ.hpp index 8f13d3632..9346ccb73 100644 --- a/Storage/Disk/DiskImage/Formats/WOZ.hpp +++ b/Storage/Disk/DiskImage/Formats/WOZ.hpp @@ -20,38 +20,38 @@ namespace Storage::Disk { Provides a @c DiskImage containing a WOZ: a bit stream representation of a floppy. */ class WOZ: public DiskImage { - public: - WOZ(const std::string &file_name); +public: + WOZ(const std::string &file_name); - // Implemented to satisfy @c DiskImage. - HeadPosition get_maximum_head_position() final; - int get_head_count() final; - std::shared_ptr get_track_at_position(Track::Address address) final; - void set_tracks(const std::map> &tracks) final; - bool get_is_read_only() final; - bool tracks_differ(Track::Address, Track::Address) final; + // Implemented to satisfy @c DiskImage. + HeadPosition get_maximum_head_position() final; + int get_head_count() final; + std::shared_ptr get_track_at_position(Track::Address address) final; + void set_tracks(const std::map> &tracks) final; + bool get_is_read_only() final; + bool tracks_differ(Track::Address, Track::Address) final; - private: - Storage::FileHolder file_; - enum class Type { - WOZ1, WOZ2 - } type_ = Type::WOZ1; - bool is_read_only_ = false; - bool is_3_5_disk_ = false; - uint8_t track_map_[160]; - long tracks_offset_ = -1; +private: + Storage::FileHolder file_; + enum class Type { + WOZ1, WOZ2 + } type_ = Type::WOZ1; + bool is_read_only_ = false; + bool is_3_5_disk_ = false; + uint8_t track_map_[160]; + long tracks_offset_ = -1; - std::vector post_crc_contents_; - CRC::CRC32 crc_generator; + std::vector post_crc_contents_; + CRC::CRC32 crc_generator; - /*! - Gets the in-file offset of a track. + /*! + Gets the in-file offset of a track. - @returns The offset within the file of the track at @c address or @c NoSuchTrack if - the track does not exit. - */ - long file_offset(Track::Address address); - constexpr static long NoSuchTrack = 0; // This is an offset a track definitely can't lie at. + @returns The offset within the file of the track at @c address or @c NoSuchTrack if + the track does not exit. + */ + long file_offset(Track::Address address); + constexpr static long NoSuchTrack = 0; // This is an offset a track definitely can't lie at. }; } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 1aef7e5a3..259133844 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -21,277 +21,277 @@ namespace Storage::Disk { class Drive: public ClockingHint::Source, public TimedEventLoop { - public: - enum class ReadyType { - /// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the motor is off. - ShugartRDY, - /// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the disk is ejected. - ShugartModifiedRDY, - /// Indicates that RDY will go active when the head steps if a disk is present; it will go inactive when the disk is ejected. - IBMRDY, - }; +public: + enum class ReadyType { + /// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the motor is off. + ShugartRDY, + /// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the disk is ejected. + ShugartModifiedRDY, + /// Indicates that RDY will go active when the head steps if a disk is present; it will go inactive when the disk is ejected. + IBMRDY, + }; - Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY); - Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY); - virtual ~Drive(); + Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY); + Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY); + virtual ~Drive(); - // TODO: Disallow copying. - // - // GCC 10 has an issue with the way the DiskII constructs its drive array if these are both - // deleted, despite not using the copy constructor. - // - // This seems to be fixed in GCC 11, so reenable this delete when possible. + // TODO: Disallow copying. + // + // GCC 10 has an issue with the way the DiskII constructs its drive array if these are both + // deleted, despite not using the copy constructor. + // + // This seems to be fixed in GCC 11, so reenable this delete when possible. // Drive(const Drive &) = delete; - void operator=(const Drive &) = delete; + void operator=(const Drive &) = delete; + + /*! + Replaces whatever is in the drive with @c disk. Supply @c nullptr to eject any current disk and leave none inserted. + */ + void set_disk(const std::shared_ptr &disk); + + /*! + @returns @c true if a disk is currently inserted; @c false otherwise. + */ + bool has_disk() const; + + /*! + @returns @c true if the drive head is currently at track zero; @c false otherwise. + */ + bool get_is_track_zero() const; + + /*! + Steps the disk head the specified number of tracks. Positive numbers step inwards (i.e. away from track 0), + negative numbers step outwards (i.e. towards track 0). + */ + void step(HeadPosition offset); + + /*! + Sets the current read head. + */ + void set_head(int head); + + /*! + Gets the head count for this disk. + */ + int get_head_count() const; + + /*! + @returns @c true if the inserted disk is read-only or no disk is inserted; @c false otherwise. + */ + bool get_is_read_only() const; + + /*! + @returns @c true if the drive is ready; @c false otherwise. + */ + bool get_is_ready() const; + + /*! + Sets whether the disk motor is on. + */ + void set_motor_on(bool); + + /*! + @returns @c true if the motor on input is active; @c false otherwise. This does not necessarily indicate whether the drive is spinning, due to momentum. + */ + bool get_motor_on() const; + + /*! + @returns @c true if the index pulse output is active; @c false otherwise. + */ + bool get_index_pulse() const; + + /*! + Begins write mode, initiating a PCM sampled region of data. Bits should be written via + @c write_bit. They will be written with the length set via @c set_expected_bit_length. + It is acceptable to supply a backlog of bits. Flux transition events will not be reported + while writing. + + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. + */ + void begin_writing(Time bit_length, bool clamp_to_index_hole); + + /*! + Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. + */ + void write_bit(bool value); + + /*! + Ends write mode, switching back to read mode. The drive will stop overwriting events. + */ + void end_writing(); + + /*! + @returns @c true if the drive has received a call to begin_writing but not yet a call to + end_writing; @c false otherwise. + */ + bool is_writing() const; + + /*! + Advances the drive by @c number_of_cycles cycles. + */ + void run_for(const Cycles cycles); + + struct Event { + Track::Event::Type type; + float length = 0.0f; + } current_event_; + + /*! + Provides a mechanism to receive track events as they occur, including the synthetic + event of "you told me to output the following data, and I've done that now". + */ + struct EventDelegate { + /// Informs the delegate that @c event has been reached. + virtual void process_event(const Event &event) = 0; /*! - Replaces whatever is in the drive with @c disk. Supply @c nullptr to eject any current disk and leave none inserted. + If the drive is in write mode, announces that all queued bits have now been written. + If the controller provides further bits now then there will be no gap in written data. */ - void set_disk(const std::shared_ptr &disk); + virtual void process_write_completed() {} - /*! - @returns @c true if a disk is currently inserted; @c false otherwise. - */ - bool has_disk() const; + /// Informs the delegate of the passing of @c cycles. + virtual void advance([[maybe_unused]] Cycles cycles) {} + }; - /*! - @returns @c true if the drive head is currently at track zero; @c false otherwise. - */ - bool get_is_track_zero() const; + /// Sets the current event delegate. + void set_event_delegate(EventDelegate *); - /*! - Steps the disk head the specified number of tracks. Positive numbers step inwards (i.e. away from track 0), - negative numbers step outwards (i.e. towards track 0). - */ - void step(HeadPosition offset); + // As per Sleeper. + ClockingHint::Preference preferred_clocking() const final; - /*! - Sets the current read head. - */ - void set_head(int head); + /// Adds an activity observer; it'll be notified of disk activity. + /// The caller can specify whether to add an LED based on disk motor. + void set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led); - /*! - Gets the head count for this disk. - */ - int get_head_count() const; + /*! + Attempts to step to the specified offset and returns the track there if one exists; an uninitialised + track otherwise. - /*! - @returns @c true if the inserted disk is read-only or no disk is inserted; @c false otherwise. - */ - bool get_is_read_only() const; + This is unambiguously **NOT A REALISTIC DRIVE FUNCTION**; real drives cannot step to a given offset. + So it is **NOT FOR HARDWARE EMULATION USAGE**. - /*! - @returns @c true if the drive is ready; @c false otherwise. - */ - bool get_is_ready() const; + It's for the benefit of user-optional fast-loading mechanisms **ONLY**. + */ + std::shared_ptr step_to(HeadPosition offset); - /*! - Sets whether the disk motor is on. - */ - void set_motor_on(bool); + /*! + Alters the rotational velocity of this drive. + */ + void set_rotation_speed(float revolutions_per_minute); - /*! - @returns @c true if the motor on input is active; @c false otherwise. This does not necessarily indicate whether the drive is spinning, due to momentum. - */ - bool get_motor_on() const; + /*! + @returns the current value of the tachometer pulse offered by some drives. + */ + bool get_tachometer() const; - /*! - @returns @c true if the index pulse output is active; @c false otherwise. - */ - bool get_index_pulse() const; +protected: + /*! + Announces the result of a step. + */ + virtual void did_step([[maybe_unused]] HeadPosition to_position) {} - /*! - Begins write mode, initiating a PCM sampled region of data. Bits should be written via - @c write_bit. They will be written with the length set via @c set_expected_bit_length. - It is acceptable to supply a backlog of bits. Flux transition events will not be reported - while writing. + /*! + Announces new media installation. - @param clamp_to_index_hole If @c true then writing will automatically be truncated by - the index hole. Writing will continue over the index hole otherwise. - */ - void begin_writing(Time bit_length, bool clamp_to_index_hole); + @c did_replace is @c true if a previous disk was replaced; @c false if the drive was previously empty. + */ + virtual void did_set_disk(bool did_replace [[maybe_unused]]) {} - /*! - Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. - */ - void write_bit(bool value); + /*! + @returns the current rotation of the disk, a float in the half-open range + 0.0 (the index hole) to 1.0 (back to the index hole, a whole rotation later). + */ + float get_rotation() const; - /*! - Ends write mode, switching back to read mode. The drive will stop overwriting events. - */ - void end_writing(); +private: + // Drives contain an entire disk; from that a certain track + // will be currently under the head. + std::shared_ptr disk_; + std::shared_ptr track_; + bool has_disk_ = false; - /*! - @returns @c true if the drive has received a call to begin_writing but not yet a call to - end_writing; @c false otherwise. - */ - bool is_writing() const; + // Contains the multiplier that converts between track-relative lengths + // to real-time lengths. So it's the reciprocal of rotation speed. + float rotational_multiplier_ = 1.0f; - /*! - Advances the drive by @c number_of_cycles cycles. - */ - void run_for(const Cycles cycles); + // A count of time since the index hole was last seen. Which is used to + // determine how far the drive is into a full rotation when switching to + // a new track. + Cycles::IntType cycles_since_index_hole_ = 0; - struct Event { - Track::Event::Type type; - float length = 0.0f; - } current_event_; + // The number of cycles that should fall within one revolution at the + // current rotation speed. + int cycles_per_revolution_ = 1; - /*! - Provides a mechanism to receive track events as they occur, including the synthetic - event of "you told me to output the following data, and I've done that now". - */ - struct EventDelegate { - /// Informs the delegate that @c event has been reached. - virtual void process_event(const Event &event) = 0; + // A record of head position and active head. + HeadPosition head_position_; + int head_ = 0; + int available_heads_ = 0; - /*! - If the drive is in write mode, announces that all queued bits have now been written. - If the controller provides further bits now then there will be no gap in written data. - */ - virtual void process_write_completed() {} + // Motor control state. + bool motor_input_is_on_ = false; + bool disk_is_rotating_ = false; + Cycles time_until_motor_transition; + void set_disk_is_rotating(bool); - /// Informs the delegate of the passing of @c cycles. - virtual void advance([[maybe_unused]] Cycles cycles) {} - }; + // Current state of the index pulse output. + Cycles index_pulse_remaining_; - /// Sets the current event delegate. - void set_event_delegate(EventDelegate *); + // If the drive is not currently reading then it is writing. While writing + // it can optionally be told to clamp to the index hole. + bool is_reading_ = true; + bool clamp_writing_to_index_hole_ = false; - // As per Sleeper. - ClockingHint::Preference preferred_clocking() const final; + // If writing is occurring then the drive will be accumulating a write segment, + // for addition to a (high-resolution) PCM track. + std::shared_ptr patched_track_; + PCMSegment write_segment_; + Time write_start_time_; - /// Adds an activity observer; it'll be notified of disk activity. - /// The caller can specify whether to add an LED based on disk motor. - void set_activity_observer(Activity::Observer *observer, const std::string &name, bool add_motor_led); + // Indicates progress towards Shugart-style drive ready states. + int ready_index_count_ = 0; + ReadyType ready_type_; + bool is_ready_ = false; - /*! - Attempts to step to the specified offset and returns the track there if one exists; an uninitialised - track otherwise. + // Maintains appropriate counting to know when to indicate that writing + // is complete. + Time cycles_until_bits_written_; + Time cycles_per_bit_; - This is unambiguously **NOT A REALISTIC DRIVE FUNCTION**; real drives cannot step to a given offset. - So it is **NOT FOR HARDWARE EMULATION USAGE**. + // TimedEventLoop call-ins and state. + void process_next_event() override; + void get_next_event(float duration_already_passed); + void advance(const Cycles cycles) override; - It's for the benefit of user-optional fast-loading mechanisms **ONLY**. - */ - std::shared_ptr step_to(HeadPosition offset); + // Helper for track changes. + float get_time_into_track() const; - /*! - Alters the rotational velocity of this drive. - */ - void set_rotation_speed(float revolutions_per_minute); + // The target (if any) for track events. + EventDelegate *event_delegate_ = nullptr; - /*! - @returns the current value of the tachometer pulse offered by some drives. - */ - bool get_tachometer() const; + /*! + @returns the track underneath the current head at the location now stepped to. + */ + std::shared_ptr get_track(); - protected: - /*! - Announces the result of a step. - */ - virtual void did_step([[maybe_unused]] HeadPosition to_position) {} + /*! + Attempts to set @c track as the track underneath the current head at the location now stepped to. + */ + void set_track(const std::shared_ptr &track); - /*! - Announces new media installation. + void setup_track(); + void invalidate_track(); - @c did_replace is @c true if a previous disk was replaced; @c false if the drive was previously empty. - */ - virtual void did_set_disk(bool did_replace [[maybe_unused]]) {} + // Activity observer description. + Activity::Observer *observer_ = nullptr; + std::string drive_name_; + bool announce_motor_led_ = false; - /*! - @returns the current rotation of the disk, a float in the half-open range - 0.0 (the index hole) to 1.0 (back to the index hole, a whole rotation later). - */ - float get_rotation() const; - - private: - // Drives contain an entire disk; from that a certain track - // will be currently under the head. - std::shared_ptr disk_; - std::shared_ptr track_; - bool has_disk_ = false; - - // Contains the multiplier that converts between track-relative lengths - // to real-time lengths. So it's the reciprocal of rotation speed. - float rotational_multiplier_ = 1.0f; - - // A count of time since the index hole was last seen. Which is used to - // determine how far the drive is into a full rotation when switching to - // a new track. - Cycles::IntType cycles_since_index_hole_ = 0; - - // The number of cycles that should fall within one revolution at the - // current rotation speed. - int cycles_per_revolution_ = 1; - - // A record of head position and active head. - HeadPosition head_position_; - int head_ = 0; - int available_heads_ = 0; - - // Motor control state. - bool motor_input_is_on_ = false; - bool disk_is_rotating_ = false; - Cycles time_until_motor_transition; - void set_disk_is_rotating(bool); - - // Current state of the index pulse output. - Cycles index_pulse_remaining_; - - // If the drive is not currently reading then it is writing. While writing - // it can optionally be told to clamp to the index hole. - bool is_reading_ = true; - bool clamp_writing_to_index_hole_ = false; - - // If writing is occurring then the drive will be accumulating a write segment, - // for addition to a (high-resolution) PCM track. - std::shared_ptr patched_track_; - PCMSegment write_segment_; - Time write_start_time_; - - // Indicates progress towards Shugart-style drive ready states. - int ready_index_count_ = 0; - ReadyType ready_type_; - bool is_ready_ = false; - - // Maintains appropriate counting to know when to indicate that writing - // is complete. - Time cycles_until_bits_written_; - Time cycles_per_bit_; - - // TimedEventLoop call-ins and state. - void process_next_event() override; - void get_next_event(float duration_already_passed); - void advance(const Cycles cycles) override; - - // Helper for track changes. - float get_time_into_track() const; - - // The target (if any) for track events. - EventDelegate *event_delegate_ = nullptr; - - /*! - @returns the track underneath the current head at the location now stepped to. - */ - std::shared_ptr get_track(); - - /*! - Attempts to set @c track as the track underneath the current head at the location now stepped to. - */ - void set_track(const std::shared_ptr &track); - - void setup_track(); - void invalidate_track(); - - // Activity observer description. - Activity::Observer *observer_ = nullptr; - std::string drive_name_; - bool announce_motor_led_ = false; - - // A rotating random data source. - uint64_t random_source_; - float random_interval_; + // A rotating random data source. + uint64_t random_source_; + float random_interval_; }; } diff --git a/Storage/Disk/Encodings/MFM/Encoder.cpp b/Storage/Disk/Encodings/MFM/Encoder.cpp index 84a73dc1d..25a9a38be 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.cpp +++ b/Storage/Disk/Encodings/MFM/Encoder.cpp @@ -80,103 +80,103 @@ enum class SurfaceItem { }; class MFMEncoder: public Encoder { - public: - MFMEncoder(std::vector &target, std::vector *fuzzy_target = nullptr) : Encoder(target, fuzzy_target) {} - virtual ~MFMEncoder() = default; +public: + MFMEncoder(std::vector &target, std::vector *fuzzy_target = nullptr) : Encoder(target, fuzzy_target) {} + virtual ~MFMEncoder() = default; - void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) final { - crc_generator_.add(input); - const uint16_t spread_value = Numeric::spread_bits(input); - const uint16_t spread_mask = Numeric::spread_bits(fuzzy_mask); - const uint16_t or_bits = uint16_t((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15)); - const uint16_t output = spread_value | ((~or_bits) & 0xaaaa); + void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) final { + crc_generator_.add(input); + const uint16_t spread_value = Numeric::spread_bits(input); + const uint16_t spread_mask = Numeric::spread_bits(fuzzy_mask); + const uint16_t or_bits = uint16_t((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15)); + const uint16_t output = spread_value | ((~or_bits) & 0xaaaa); - output_short(output, spread_mask); + output_short(output, spread_mask); + } + + void add_index_address_mark() final { + for(int c = 0; c < 3; c++) output_short(MFMIndexSync); + add_byte(IndexAddressByte); + } + + void add_ID_address_mark() final { + output_sync(); + add_byte(IDAddressByte); + } + + void add_data_address_mark() final { + output_sync(); + add_byte(DataAddressByte); + } + + void add_deleted_data_address_mark() final { + output_sync(); + add_byte(DeletedDataAddressByte); + } + + size_t item_size(SurfaceItem item) { + switch(item) { + case SurfaceItem::Mark: return 8; // Three syncs plus the mark type. + case SurfaceItem::Data: return 2; // Just a single encoded byte. + default: assert(false); } + return 0; // Should be impossible to reach in debug builds. + } - void add_index_address_mark() final { - for(int c = 0; c < 3; c++) output_short(MFMIndexSync); - add_byte(IndexAddressByte); - } +private: + uint16_t last_output_; + void output_short(uint16_t value, uint16_t fuzzy_mask = 0) final { + last_output_ = value; + Encoder::output_short(value, fuzzy_mask); + } - void add_ID_address_mark() final { - output_sync(); - add_byte(IDAddressByte); - } - - void add_data_address_mark() final { - output_sync(); - add_byte(DataAddressByte); - } - - void add_deleted_data_address_mark() final { - output_sync(); - add_byte(DeletedDataAddressByte); - } - - size_t item_size(SurfaceItem item) { - switch(item) { - case SurfaceItem::Mark: return 8; // Three syncs plus the mark type. - case SurfaceItem::Data: return 2; // Just a single encoded byte. - default: assert(false); - } - return 0; // Should be impossible to reach in debug builds. - } - - private: - uint16_t last_output_; - void output_short(uint16_t value, uint16_t fuzzy_mask = 0) final { - last_output_ = value; - Encoder::output_short(value, fuzzy_mask); - } - - void output_sync() { - for(int c = 0; c < 3; c++) output_short(MFMSync); - crc_generator_.set_value(MFMPostSyncCRCValue); - } + void output_sync() { + for(int c = 0; c < 3; c++) output_short(MFMSync); + crc_generator_.set_value(MFMPostSyncCRCValue); + } }; class FMEncoder: public Encoder { - // encodes each 16-bit part as clock, data, clock, data [...] - public: - FMEncoder(std::vector &target, std::vector *fuzzy_target = nullptr) : Encoder(target, fuzzy_target) {} +// encodes each 16-bit part as clock, data, clock, data [...] +public: + FMEncoder(std::vector &target, std::vector *fuzzy_target = nullptr) : Encoder(target, fuzzy_target) {} - void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) final { - crc_generator_.add(input); - output_short( - Numeric::spread_bits(input) | 0xaaaa, - Numeric::spread_bits(fuzzy_mask) - ); - } + void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) final { + crc_generator_.add(input); + output_short( + Numeric::spread_bits(input) | 0xaaaa, + Numeric::spread_bits(fuzzy_mask) + ); + } - void add_index_address_mark() final { - crc_generator_.reset(); - crc_generator_.add(IndexAddressByte); - output_short(FMIndexAddressMark); - } + void add_index_address_mark() final { + crc_generator_.reset(); + crc_generator_.add(IndexAddressByte); + output_short(FMIndexAddressMark); + } - void add_ID_address_mark() final { - crc_generator_.reset(); - crc_generator_.add(IDAddressByte); - output_short(FMIDAddressMark); - } + void add_ID_address_mark() final { + crc_generator_.reset(); + crc_generator_.add(IDAddressByte); + output_short(FMIDAddressMark); + } - void add_data_address_mark() final { - crc_generator_.reset(); - crc_generator_.add(DataAddressByte); - output_short(FMDataAddressMark); - } + void add_data_address_mark() final { + crc_generator_.reset(); + crc_generator_.add(DataAddressByte); + output_short(FMDataAddressMark); + } - void add_deleted_data_address_mark() final { - crc_generator_.reset(); - crc_generator_.add(DeletedDataAddressByte); - output_short(FMDeletedDataAddressMark); - } + void add_deleted_data_address_mark() final { + crc_generator_.reset(); + crc_generator_.add(DeletedDataAddressByte); + output_short(FMDeletedDataAddressMark); + } - size_t item_size(SurfaceItem) { - // Marks are just slightly-invalid bytes, so everything is the same length. - return 2; - } + size_t item_size(SurfaceItem) { + // Marks are just slightly-invalid bytes, so everything is the same length. + return 2; + } }; template std::shared_ptr diff --git a/Storage/Disk/Encodings/MFM/Encoder.hpp b/Storage/Disk/Encodings/MFM/Encoder.hpp index 57e821526..88ab4f38a 100644 --- a/Storage/Disk/Encodings/MFM/Encoder.hpp +++ b/Storage/Disk/Encodings/MFM/Encoder.hpp @@ -40,38 +40,38 @@ std::shared_ptr TrackWithSectors( std::optional sector_gap_filler_byte = std::nullopt); class Encoder { - public: - Encoder(std::vector &target, std::vector *fuzzy_target); - virtual ~Encoder() = default; - virtual void reset_target(std::vector &target, std::vector *fuzzy_target = nullptr); +public: + Encoder(std::vector &target, std::vector *fuzzy_target); + virtual ~Encoder() = default; + virtual void reset_target(std::vector &target, std::vector *fuzzy_target = nullptr); - virtual void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) = 0; - virtual void add_index_address_mark() = 0; - virtual void add_ID_address_mark() = 0; - virtual void add_data_address_mark() = 0; - virtual void add_deleted_data_address_mark() = 0; - virtual void output_short(uint16_t value, uint16_t fuzzy_mask = 0); + virtual void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) = 0; + virtual void add_index_address_mark() = 0; + virtual void add_ID_address_mark() = 0; + virtual void add_data_address_mark() = 0; + virtual void add_deleted_data_address_mark() = 0; + virtual void output_short(uint16_t value, uint16_t fuzzy_mask = 0); - template void add_bytes(IteratorT begin, IteratorT end) { - while(begin != end) { - add_byte(*begin); - ++begin; - } + template void add_bytes(IteratorT begin, IteratorT end) { + while(begin != end) { + add_byte(*begin); + ++begin; } + } - template void add_bytes(const ContainerT &container) { - write(std::begin(container), std::end(container)); - } + template void add_bytes(const ContainerT &container) { + write(std::begin(container), std::end(container)); + } - /// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC. - void add_crc(bool incorrectly); + /// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC. + void add_crc(bool incorrectly); - protected: - CRC::CCITT crc_generator_; +protected: + CRC::CCITT crc_generator_; - private: - std::vector *target_ = nullptr; - std::vector *fuzzy_target_ = nullptr; +private: + std::vector *target_ = nullptr; + std::vector *fuzzy_target_ = nullptr; }; std::unique_ptr GetMFMEncoder(std::vector &target, std::vector *fuzzy_target = nullptr); diff --git a/Storage/Disk/Encodings/MFM/Parser.hpp b/Storage/Disk/Encodings/MFM/Parser.hpp index 24edfc5ca..a528d81c2 100644 --- a/Storage/Disk/Encodings/MFM/Parser.hpp +++ b/Storage/Disk/Encodings/MFM/Parser.hpp @@ -22,45 +22,45 @@ namespace Storage::Encodings::MFM { Provides a mechanism for collecting sectors from a disk. */ class Parser { - public: - /// Creates a parser that will only attempt to interpret the underlying disk as being of @c density. - Parser(Density density, const std::shared_ptr &disk); +public: + /// Creates a parser that will only attempt to interpret the underlying disk as being of @c density. + Parser(Density density, const std::shared_ptr &disk); - /// Creates a parser that will automatically try all available FM and MFM densities to try to extract sectors. - Parser(const std::shared_ptr &disk); + /// Creates a parser that will automatically try all available FM and MFM densities to try to extract sectors. + Parser(const std::shared_ptr &disk); - /*! - Seeks to the physical track at @c head and @c track. Searches on it for a sector - with logical address @c sector. + /*! + Seeks to the physical track at @c head and @c track. Searches on it for a sector + with logical address @c sector. - @returns a sector if one was found; @c nullptr otherwise. - */ - const Storage::Encodings::MFM::Sector *sector(int head, int track, uint8_t sector); + @returns a sector if one was found; @c nullptr otherwise. + */ + const Storage::Encodings::MFM::Sector *sector(int head, int track, uint8_t sector); - /*! - Seeks to the physical track at @c head and @c track. Searches on it for any sector. + /*! + Seeks to the physical track at @c head and @c track. Searches on it for any sector. - @returns a sector if one was found; @c nullptr otherwise. - */ - const Storage::Encodings::MFM::Sector *any_sector(int head, int track); + @returns a sector if one was found; @c nullptr otherwise. + */ + const Storage::Encodings::MFM::Sector *any_sector(int head, int track); - // TODO: set_sector. + // TODO: set_sector. - private: - std::shared_ptr disk_; - std::optional density_; +private: + std::shared_ptr disk_; + std::optional density_; - void install_track(const Storage::Disk::Track::Address &address); - static SectorMap parse_track(const Storage::Disk::Track &track, Density density); - static void append(const SectorMap &source, std::map &destination); + void install_track(const Storage::Disk::Track::Address &address); + static SectorMap parse_track(const Storage::Disk::Track &track, Density density); + static void append(const SectorMap &source, std::map &destination); - // Maps from a track address, i.e. head and position, to a map from - // sector IDs to sectors. - std::map< - Storage::Disk::Track::Address, - std::map - > sectors_by_address_by_track_; + // Maps from a track address, i.e. head and position, to a map from + // sector IDs to sectors. + std::map< + Storage::Disk::Track::Address, + std::map + > sectors_by_address_by_track_; }; } diff --git a/Storage/Disk/Encodings/MFM/Shifter.hpp b/Storage/Disk/Encodings/MFM/Shifter.hpp index 63e3447f0..f24fcc970 100644 --- a/Storage/Disk/Encodings/MFM/Shifter.hpp +++ b/Storage/Disk/Encodings/MFM/Shifter.hpp @@ -43,38 +43,38 @@ namespace Storage::Encodings::MFM { A specific instance of the CRC generator can be supplied at construction if preferred. */ class Shifter { - public: - Shifter(); - Shifter(CRC::CCITT *crc_generator); +public: + Shifter(); + Shifter(CRC::CCITT *crc_generator); - void set_is_mfm(bool is_mfm); - void set_should_obey_syncs(bool should_obey_syncs); - void add_input_bit(int bit); + void set_is_mfm(bool is_mfm); + void set_should_obey_syncs(bool should_obey_syncs); + void add_input_bit(int bit); - enum Token { - Index, ID, Data, DeletedData, Sync, Byte, None - }; - uint8_t get_byte() const; - Token get_token() const { - return token_; - } - CRC::CCITT &get_crc_generator() { - return *crc_generator_; - } + enum Token { + Index, ID, Data, DeletedData, Sync, Byte, None + }; + uint8_t get_byte() const; + Token get_token() const { + return token_; + } + CRC::CCITT &get_crc_generator() { + return *crc_generator_; + } - private: - // Bit stream input state. - int bits_since_token_ = 0; - unsigned int shift_register_ = 0; - bool is_awaiting_marker_value_ = false; - bool should_obey_syncs_ = true; - Token token_ = None; +private: + // Bit stream input state. + int bits_since_token_ = 0; + unsigned int shift_register_ = 0; + bool is_awaiting_marker_value_ = false; + bool should_obey_syncs_ = true; + Token token_ = None; - // Input configuration. - bool is_mfm_ = false; + // Input configuration. + bool is_mfm_ = false; - std::unique_ptr owned_crc_generator_; - CRC::CCITT *crc_generator_; + std::unique_ptr owned_crc_generator_; + CRC::CCITT *crc_generator_; }; } diff --git a/Storage/Disk/Track/PCMSegment.hpp b/Storage/Disk/Track/PCMSegment.hpp index 44b605989..e9b4d8e60 100644 --- a/Storage/Disk/Track/PCMSegment.hpp +++ b/Storage/Disk/Track/PCMSegment.hpp @@ -151,55 +151,55 @@ struct PCMSegment { Provides a stream of events by inspecting a PCMSegment. */ class PCMSegmentEventSource { - public: - /*! - Constructs a @c PCMSegmentEventSource that will derive events from @c segment. - The event source is initially @c reset. - */ - PCMSegmentEventSource(const PCMSegment &); +public: + /*! + Constructs a @c PCMSegmentEventSource that will derive events from @c segment. + The event source is initially @c reset. + */ + PCMSegmentEventSource(const PCMSegment &); - /*! - Copy constructor; produces a segment event source with the same underlying segment - but a unique pointer into it. - */ - PCMSegmentEventSource(const PCMSegmentEventSource &); - PCMSegmentEventSource &operator =(const PCMSegmentEventSource &); + /*! + Copy constructor; produces a segment event source with the same underlying segment + but a unique pointer into it. + */ + PCMSegmentEventSource(const PCMSegmentEventSource &); + PCMSegmentEventSource &operator =(const PCMSegmentEventSource &); - /*! - @returns the next event that will occur in this event stream. - */ - Track::Event get_next_event(); + /*! + @returns the next event that will occur in this event stream. + */ + Track::Event get_next_event(); - /*! - Resets the event source to the beginning of its event stream, exactly as if - it has just been constructed. - */ - void reset(); + /*! + Resets the event source to the beginning of its event stream, exactly as if + it has just been constructed. + */ + void reset(); - /*! - Seeks as close to @c time_from_start as the event source can manage while not - exceeding it. + /*! + Seeks as close to @c time_from_start as the event source can manage while not + exceeding it. - @returns the time the source is now at. - */ - float seek_to(float time_from_start); + @returns the time the source is now at. + */ + float seek_to(float time_from_start); - /*! - @returns the total length of the stream of data that the source will provide. - */ - Time get_length(); + /*! + @returns the total length of the stream of data that the source will provide. + */ + Time get_length(); - /*! - @returns a reference to the underlying segment. - */ - const PCMSegment &segment() const; - PCMSegment &segment(); + /*! + @returns a reference to the underlying segment. + */ + const PCMSegment &segment() const; + PCMSegment &segment(); - private: - std::shared_ptr segment_; - std::size_t bit_pointer_; - Track::Event next_event_; - Numeric::LFSR lfsr_; +private: + std::shared_ptr segment_; + std::size_t bit_pointer_; + Track::Event next_event_; + Numeric::LFSR lfsr_; }; } diff --git a/Storage/Disk/Track/PCMTrack.hpp b/Storage/Disk/Track/PCMTrack.hpp index d9159c30f..ffb3672e0 100644 --- a/Storage/Disk/Track/PCMTrack.hpp +++ b/Storage/Disk/Track/PCMTrack.hpp @@ -24,67 +24,67 @@ namespace Storage::Disk { multiple distinct segments of data, each with a separate clock rate. */ class PCMTrack: public Track { - public: - /*! - Creates a @c PCMTrack consisting of multiple segments of data, permitting multiple clock rates. - */ - PCMTrack(const std::vector &); +public: + /*! + Creates a @c PCMTrack consisting of multiple segments of data, permitting multiple clock rates. + */ + PCMTrack(const std::vector &); - /*! - Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate. - The segment's @c length_of_a_bit will be ignored and therefore need not be filled in. - */ - PCMTrack(const PCMSegment &); + /*! + Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate. + The segment's @c length_of_a_bit will be ignored and therefore need not be filled in. + */ + PCMTrack(const PCMSegment &); - /*! - Copy constructor; required for Tracks in order to support modifiable disks. - */ - PCMTrack(const PCMTrack &); + /*! + Copy constructor; required for Tracks in order to support modifiable disks. + */ + PCMTrack(const PCMTrack &); - /*! - Creates a PCMTrack by sampling the original at a rate of @c bits_per_track. - */ - static PCMTrack *resampled_clone(Track *original, size_t bits_per_track); + /*! + Creates a PCMTrack by sampling the original at a rate of @c bits_per_track. + */ + static PCMTrack *resampled_clone(Track *original, size_t bits_per_track); - // as per @c Track - Event get_next_event() final; - float seek_to(float time_since_index_hole) final; - Track *clone() const final; + // as per @c Track + Event get_next_event() final; + float seek_to(float time_since_index_hole) final; + Track *clone() const final; - // Obtains a copy of this track, flattened to a single PCMSegment, which - // consists of @c bits_per_track potential flux transition points. - PCMTrack *resampled_clone(size_t bits_per_track); - bool is_resampled_clone(); + // Obtains a copy of this track, flattened to a single PCMSegment, which + // consists of @c bits_per_track potential flux transition points. + PCMTrack *resampled_clone(size_t bits_per_track); + bool is_resampled_clone(); - /*! - Replaces whatever is currently on the track from @c start_position to @c start_position + segment length - with the contents of @c segment. + /*! + Replaces whatever is currently on the track from @c start_position to @c start_position + segment length + with the contents of @c segment. - This is a well-defined operation only for tracks with a single segment. The new segment will be resampled - to the track's underlying segment, which will be mutated. + This is a well-defined operation only for tracks with a single segment. The new segment will be resampled + to the track's underlying segment, which will be mutated. - @param start_time The time at which this segment begins. Must be in the range [0, 1). - @param segment The PCM segment to add. - @param clamp_to_index_hole If @c true then the new segment will be truncated if it overruns the index hole; - it will otherwise write over the index hole and continue. - */ - void add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole); + @param start_time The time at which this segment begins. Must be in the range [0, 1). + @param segment The PCM segment to add. + @param clamp_to_index_hole If @c true then the new segment will be truncated if it overruns the index hole; + it will otherwise write over the index hole and continue. + */ + void add_segment(const Time &start_time, const PCMSegment &segment, bool clamp_to_index_hole); - private: - /*! - Creates a PCMTrack with a single segment, consisting of @c bits_per_track flux windows, - initialised with no flux events. - */ - PCMTrack(unsigned int bits_per_track); +private: + /*! + Creates a PCMTrack with a single segment, consisting of @c bits_per_track flux windows, + initialised with no flux events. + */ + PCMTrack(unsigned int bits_per_track); - // storage for the segments that describe this track - std::vector segment_event_sources_; + // storage for the segments that describe this track + std::vector segment_event_sources_; - // a pointer to the first bit to consider as the next event - std::size_t segment_pointer_; + // a pointer to the first bit to consider as the next event + std::size_t segment_pointer_; - PCMTrack(); - bool is_resampled_clone_ = false; + PCMTrack(); + bool is_resampled_clone_ = false; }; } diff --git a/Storage/Disk/Track/Track.hpp b/Storage/Disk/Track/Track.hpp index cc1b5a9d6..2c032c5bd 100644 --- a/Storage/Disk/Track/Track.hpp +++ b/Storage/Disk/Track/Track.hpp @@ -17,47 +17,47 @@ namespace Storage::Disk { Contains a head position, with some degree of sub-integral precision. */ class HeadPosition { - public: - /// Creates an instance decribing position @c value at a resolution of @c scale ticks per track. - constexpr HeadPosition(int value, int scale) : position_(value * (4/scale)) {} - constexpr explicit HeadPosition(int value) : HeadPosition(value, 1) {} - constexpr HeadPosition() : HeadPosition(0) {} +public: + /// Creates an instance decribing position @c value at a resolution of @c scale ticks per track. + constexpr HeadPosition(int value, int scale) : position_(value * (4/scale)) {} + constexpr explicit HeadPosition(int value) : HeadPosition(value, 1) {} + constexpr HeadPosition() : HeadPosition(0) {} - /// @returns the whole number part of the position. - constexpr int as_int() const { return position_ >> 2; } - /// @returns n where n/2 is the head position. - constexpr int as_half() const { return position_ >> 1; } - /// @returns n where n/4 is the head position. - constexpr int as_quarter() const { return position_; } + /// @returns the whole number part of the position. + constexpr int as_int() const { return position_ >> 2; } + /// @returns n where n/2 is the head position. + constexpr int as_half() const { return position_ >> 1; } + /// @returns n where n/4 is the head position. + constexpr int as_quarter() const { return position_; } - /// @returns the head position at maximal but unspecified precision. - constexpr int as_largest() const { return as_quarter(); } + /// @returns the head position at maximal but unspecified precision. + constexpr int as_largest() const { return as_quarter(); } - HeadPosition &operator +=(const HeadPosition &rhs) { - position_ += rhs.position_; - return *this; - } - constexpr bool operator ==(const HeadPosition &rhs) const { - return position_ == rhs.position_; - } - constexpr bool operator !=(const HeadPosition &rhs) const { - return position_ != rhs.position_; - } - constexpr bool operator <(const HeadPosition &rhs) const { - return position_ < rhs.position_; - } - constexpr bool operator <=(const HeadPosition &rhs) const { - return position_ <= rhs.position_; - } - constexpr bool operator >(const HeadPosition &rhs) const { - return position_ > rhs.position_; - } - constexpr bool operator >=(const HeadPosition &rhs) const { - return position_ >= rhs.position_; - } + HeadPosition &operator +=(const HeadPosition &rhs) { + position_ += rhs.position_; + return *this; + } + constexpr bool operator ==(const HeadPosition &rhs) const { + return position_ == rhs.position_; + } + constexpr bool operator !=(const HeadPosition &rhs) const { + return position_ != rhs.position_; + } + constexpr bool operator <(const HeadPosition &rhs) const { + return position_ < rhs.position_; + } + constexpr bool operator <=(const HeadPosition &rhs) const { + return position_ <= rhs.position_; + } + constexpr bool operator >(const HeadPosition &rhs) const { + return position_ > rhs.position_; + } + constexpr bool operator >=(const HeadPosition &rhs) const { + return position_ >= rhs.position_; + } - private: - int position_ = 0; +private: + int position_ = 0; }; /*! @@ -67,61 +67,61 @@ class HeadPosition { Subclasses should implement @c get_next_event. */ class Track { - public: - virtual ~Track() = default; +public: + virtual ~Track() = default; - /*! - Describes the location of a track, implementing < to allow for use as a set key. - */ - struct Address { - int head; - HeadPosition position; + /*! + Describes the location of a track, implementing < to allow for use as a set key. + */ + struct Address { + int head; + HeadPosition position; - constexpr bool operator < (const Address &rhs) const { - int largest_position = position.as_largest(); - int rhs_largest_position = rhs.position.as_largest(); - return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position); - } - constexpr bool operator == (const Address &rhs) const { - return head == rhs.head && position == rhs.position; - } - constexpr bool operator != (const Address &rhs) const { - return head != rhs.head || position != rhs.position; - } + constexpr bool operator < (const Address &rhs) const { + int largest_position = position.as_largest(); + int rhs_largest_position = rhs.position.as_largest(); + return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position); + } + constexpr bool operator == (const Address &rhs) const { + return head == rhs.head && position == rhs.position; + } + constexpr bool operator != (const Address &rhs) const { + return head != rhs.head || position != rhs.position; + } - constexpr Address(int head, HeadPosition position) : head(head), position(position) {} - }; + constexpr Address(int head, HeadPosition position) : head(head), position(position) {} + }; - /*! - Describes a detectable track event: either a flux transition or the passing of the index hole, - along with the length of time between the previous event and its occurance. + /*! + Describes a detectable track event: either a flux transition or the passing of the index hole, + along with the length of time between the previous event and its occurance. - The sum of all lengths of time across an entire track should be 1; if an event is said to be - 1/3 away then that means 1/3 of a rotation. - */ - struct Event { - enum Type { - IndexHole, FluxTransition - } type; - Time length; - }; + The sum of all lengths of time across an entire track should be 1; if an event is said to be + 1/3 away then that means 1/3 of a rotation. + */ + struct Event { + enum Type { + IndexHole, FluxTransition + } type; + Time length; + }; - /*! - @returns the next event that will be detected during rotation of this disk. - */ - virtual Event get_next_event() = 0; + /*! + @returns the next event that will be detected during rotation of this disk. + */ + virtual Event get_next_event() = 0; - /*! - Jumps to the start of the fist event that will occur after @c time_since_index_hole. + /*! + Jumps to the start of the fist event that will occur after @c time_since_index_hole. - @returns the time jumped to. - */ - virtual float seek_to(float time_since_index_hole) = 0; + @returns the time jumped to. + */ + virtual float seek_to(float time_since_index_hole) = 0; - /*! - The virtual copy constructor pattern; returns a copy of the Track. - */ - virtual Track *clone() const = 0; + /*! + The virtual copy constructor pattern; returns a copy of the Track. + */ + virtual Track *clone() const = 0; }; } diff --git a/Storage/Disk/Track/UnformattedTrack.hpp b/Storage/Disk/Track/UnformattedTrack.hpp index 2bb14f2d9..40bd7499d 100644 --- a/Storage/Disk/Track/UnformattedTrack.hpp +++ b/Storage/Disk/Track/UnformattedTrack.hpp @@ -16,10 +16,10 @@ namespace Storage::Disk { A subclass of @c Track with no contents. Just an index hole. */ class UnformattedTrack: public Track { - public: - Event get_next_event() final; - float seek_to(float time_since_index_hole) final; - Track *clone() const final; +public: + Event get_next_event() final; + float seek_to(float time_since_index_hole) final; + Track *clone() const final; }; } diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index d5faf7cb9..2749a44e8 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -19,232 +19,232 @@ namespace Storage { class FileHolder final { +public: + enum class Error { + CantOpen = -1 + }; + + enum class FileMode { + ReadWrite, + Read, + Rewrite + }; + + ~FileHolder(); + + /*! + Attempts to open the file indicated by @c file_name. @c ideal_mode nominates how the file would + most ideally be opened. It can be one of: + + ReadWrite attempt to open this file for random access reading and writing. If that fails, + will attept to open in Read mode. + Read attempts to open this file for reading only. + Rewrite opens the file for rewriting; none of the original content is preserved; whatever + the caller outputs will replace the existing file. + + @throws ErrorCantOpen if the file cannot be opened. + */ + FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite); + + /*! + Performs @c get8 four times on @c file, casting each result to a @c uint32_t + and returning the four assembled in little endian order. + */ + uint32_t get32le(); + + /*! + Writes @c value using successive @c put8s, in little endian order. + */ + template void put_le(T value) { + auto bytes = sizeof(T); + while(bytes--) { + put8(value&0xff); + value >>= 8; + } + } + + /*! + Writes @c value using successive @c put8s, in big endian order. + */ + template void put_be(T value) { + auto shift = sizeof(T) * 8; + while(shift) { + shift -= 8; + put8((value >> shift)&0xff); + } + } + + /*! + Performs @c get8 four times on @c file, casting each result to a @c uint32_t + and returning the four assembled in big endian order. + */ + uint32_t get32be(); + + /*! + Performs @c get8 three times on @c file, casting each result to a @c uint32_t + and returning the three assembled in little endian order. + */ + uint32_t get24le(); + + /*! + Performs @c get8 three times on @c file, casting each result to a @c uint32_t + and returning the three assembled in big endian order. + */ + uint32_t get24be(); + + /*! + Performs @c get8 two times on @c file, casting each result to a @c uint32_t + and returning the two assembled in little endian order. + */ + uint16_t get16le(); + + /*! + Writes @c value using two successive @c put8s, in little endian order. + */ + void put16le(uint16_t value); + + /*! + Performs @c get8 two times on @c file, casting each result to a @c uint32_t + and returning the two assembled in big endian order. + */ + uint16_t get16be(); + + /*! + Writes @c value using two successive @c put8s, in big endian order. + */ + void put16be(uint16_t value); + + /*! Reads a single byte from @c file. */ + uint8_t get8(); + + /*! Writes a single byte from @c file. */ + void put8(uint8_t value); + + /*! Writes @c value a total of @c repeats times. */ + void putn(std::size_t repeats, uint8_t value); + + /*! Reads @c size bytes and returns them as a vector. */ + std::vector read(std::size_t size); + + /*! Reads @c a.size() bytes into @c a.data(). */ + template std::size_t read(std::array &a) { + return read(a.data(), a.size()); + } + + /*! Reads @c size bytes and writes them to @c buffer. */ + std::size_t read(uint8_t *buffer, std::size_t size); + + /*! Writes @c buffer one byte at a time in order. */ + std::size_t write(const std::vector &buffer); + + /*! Writes @c buffer one byte at a time in order, writing @c size bytes in total. */ + std::size_t write(const uint8_t *buffer, std::size_t size); + + /*! Moves @c bytes from the anchor indicated by @c whence: SEEK_SET, SEEK_CUR or SEEK_END. */ + void seek(long offset, int whence); + + /*! @returns The current cursor position within this file. */ + long tell(); + + /*! Flushes any queued content that has not yet been written to disk. */ + void flush(); + + /*! @returns @c true if the end-of-file indicator is set, @c false otherwise. */ + bool eof(); + + class BitStream { public: - enum class Error { - CantOpen = -1 - }; - - enum class FileMode { - ReadWrite, - Read, - Rewrite - }; - - ~FileHolder(); - - /*! - Attempts to open the file indicated by @c file_name. @c ideal_mode nominates how the file would - most ideally be opened. It can be one of: - - ReadWrite attempt to open this file for random access reading and writing. If that fails, - will attept to open in Read mode. - Read attempts to open this file for reading only. - Rewrite opens the file for rewriting; none of the original content is preserved; whatever - the caller outputs will replace the existing file. - - @throws ErrorCantOpen if the file cannot be opened. - */ - FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite); - - /*! - Performs @c get8 four times on @c file, casting each result to a @c uint32_t - and returning the four assembled in little endian order. - */ - uint32_t get32le(); - - /*! - Writes @c value using successive @c put8s, in little endian order. - */ - template void put_le(T value) { - auto bytes = sizeof(T); - while(bytes--) { - put8(value&0xff); - value >>= 8; + uint8_t get_bits(int q) { + uint8_t result = 0; + while(q--) { + result = uint8_t((result << 1) | get_bit()); } + return result; } - /*! - Writes @c value using successive @c put8s, in big endian order. - */ - template void put_be(T value) { - auto shift = sizeof(T) * 8; - while(shift) { - shift -= 8; - put8((value >> shift)&0xff); - } - } - - /*! - Performs @c get8 four times on @c file, casting each result to a @c uint32_t - and returning the four assembled in big endian order. - */ - uint32_t get32be(); - - /*! - Performs @c get8 three times on @c file, casting each result to a @c uint32_t - and returning the three assembled in little endian order. - */ - uint32_t get24le(); - - /*! - Performs @c get8 three times on @c file, casting each result to a @c uint32_t - and returning the three assembled in big endian order. - */ - uint32_t get24be(); - - /*! - Performs @c get8 two times on @c file, casting each result to a @c uint32_t - and returning the two assembled in little endian order. - */ - uint16_t get16le(); - - /*! - Writes @c value using two successive @c put8s, in little endian order. - */ - void put16le(uint16_t value); - - /*! - Performs @c get8 two times on @c file, casting each result to a @c uint32_t - and returning the two assembled in big endian order. - */ - uint16_t get16be(); - - /*! - Writes @c value using two successive @c put8s, in big endian order. - */ - void put16be(uint16_t value); - - /*! Reads a single byte from @c file. */ - uint8_t get8(); - - /*! Writes a single byte from @c file. */ - void put8(uint8_t value); - - /*! Writes @c value a total of @c repeats times. */ - void putn(std::size_t repeats, uint8_t value); - - /*! Reads @c size bytes and returns them as a vector. */ - std::vector read(std::size_t size); - - /*! Reads @c a.size() bytes into @c a.data(). */ - template std::size_t read(std::array &a) { - return read(a.data(), a.size()); - } - - /*! Reads @c size bytes and writes them to @c buffer. */ - std::size_t read(uint8_t *buffer, std::size_t size); - - /*! Writes @c buffer one byte at a time in order. */ - std::size_t write(const std::vector &buffer); - - /*! Writes @c buffer one byte at a time in order, writing @c size bytes in total. */ - std::size_t write(const uint8_t *buffer, std::size_t size); - - /*! Moves @c bytes from the anchor indicated by @c whence: SEEK_SET, SEEK_CUR or SEEK_END. */ - void seek(long offset, int whence); - - /*! @returns The current cursor position within this file. */ - long tell(); - - /*! Flushes any queued content that has not yet been written to disk. */ - void flush(); - - /*! @returns @c true if the end-of-file indicator is set, @c false otherwise. */ - bool eof(); - - class BitStream { - public: - uint8_t get_bits(int q) { - uint8_t result = 0; - while(q--) { - result = uint8_t((result << 1) | get_bit()); - } - return result; - } - - private: - BitStream(FILE *file, bool lsb_first) : - file_(file), - lsb_first_(lsb_first), - next_value_(0), - bits_remaining_(0) {} - friend FileHolder; - - FILE *file_; - bool lsb_first_; - uint8_t next_value_; - int bits_remaining_; - - uint8_t get_bit() { - if(!bits_remaining_) { - bits_remaining_ = 8; - next_value_ = uint8_t(fgetc(file_)); - } - - uint8_t bit; - if(lsb_first_) { - bit = next_value_ & 1; - next_value_ >>= 1; - } else { - bit = next_value_ >> 7; - next_value_ <<= 1; - } - - bits_remaining_--; - - return bit; - } - }; - - /*! - Obtains a BitStream for reading from the file from the current reading cursor. - */ - BitStream get_bitstream(bool lsb_first); - - /*! - Reads @c length bytes from the file and compares them to the first - @c length bytes of @c signature. If @c length is 0, it is computed - as the length of @c signature not including the terminating null. - - @returns @c true if the bytes read match the signature; @c false otherwise. - */ - bool check_signature(const char *signature, std::size_t length = 0); - - /*! - Determines and returns the file extension: everything from the final character - back to the first dot. The string is converted to lowercase before being returned. - */ - std::string extension(); - - /*! - Ensures the file is at least @c length bytes long, appending 0s until it is - if necessary. - */ - void ensure_is_at_least_length(long length); - - /*! - @returns @c true if an attempt was made to read this file in ReadWrite mode but it could be opened only for reading; @c false otherwise. - */ - bool get_is_known_read_only(); - - /*! - @returns the stat struct describing this file. - */ - struct stat &stats(); - - /*! - @returns a mutex owned by the file that can be used to serialise file access. - */ - std::mutex &get_file_access_mutex(); - private: - FILE *file_ = nullptr; - const std::string name_; + BitStream(FILE *file, bool lsb_first) : + file_(file), + lsb_first_(lsb_first), + next_value_(0), + bits_remaining_(0) {} + friend FileHolder; - struct stat file_stats_; - bool is_read_only_ = false; + FILE *file_; + bool lsb_first_; + uint8_t next_value_; + int bits_remaining_; - std::mutex file_access_mutex_; + uint8_t get_bit() { + if(!bits_remaining_) { + bits_remaining_ = 8; + next_value_ = uint8_t(fgetc(file_)); + } + + uint8_t bit; + if(lsb_first_) { + bit = next_value_ & 1; + next_value_ >>= 1; + } else { + bit = next_value_ >> 7; + next_value_ <<= 1; + } + + bits_remaining_--; + + return bit; + } + }; + + /*! + Obtains a BitStream for reading from the file from the current reading cursor. + */ + BitStream get_bitstream(bool lsb_first); + + /*! + Reads @c length bytes from the file and compares them to the first + @c length bytes of @c signature. If @c length is 0, it is computed + as the length of @c signature not including the terminating null. + + @returns @c true if the bytes read match the signature; @c false otherwise. + */ + bool check_signature(const char *signature, std::size_t length = 0); + + /*! + Determines and returns the file extension: everything from the final character + back to the first dot. The string is converted to lowercase before being returned. + */ + std::string extension(); + + /*! + Ensures the file is at least @c length bytes long, appending 0s until it is + if necessary. + */ + void ensure_is_at_least_length(long length); + + /*! + @returns @c true if an attempt was made to read this file in ReadWrite mode but it could be opened only for reading; @c false otherwise. + */ + bool get_is_known_read_only(); + + /*! + @returns the stat struct describing this file. + */ + struct stat &stats(); + + /*! + @returns a mutex owned by the file that can be used to serialise file access. + */ + std::mutex &get_file_access_mutex(); + +private: + FILE *file_ = nullptr; + const std::string name_; + + struct stat file_stats_; + bool is_read_only_ = false; + + std::mutex file_access_mutex_; }; } diff --git a/Storage/MassStorage/Encodings/ApplePartitionMap.hpp b/Storage/MassStorage/Encodings/ApplePartitionMap.hpp index 65b337a8b..f770dc5e4 100644 --- a/Storage/MassStorage/Encodings/ApplePartitionMap.hpp +++ b/Storage/MassStorage/Encodings/ApplePartitionMap.hpp @@ -27,222 +27,222 @@ enum class DriveType { and device driver information if applicable. */ template class PartitionMap { - public: - /*! - Sets the drive type to map to and the number of blocks in the underlying partition. - */ - void set_drive_type(DriveType drive_type, size_t number_of_blocks) { - drive_type_ = drive_type; - number_of_blocks_ = number_of_blocks; - } +public: + /*! + Sets the drive type to map to and the number of blocks in the underlying partition. + */ + void set_drive_type(DriveType drive_type, size_t number_of_blocks) { + drive_type_ = drive_type; + number_of_blocks_ = number_of_blocks; + } - /*! - @returns The total number of blocks on the entire volume. - */ - size_t get_number_of_blocks() const { - return - number_of_blocks_ + // Size of the volume. - size_t(non_volume_blocks()); // Size of everything else. - } + /*! + @returns The total number of blocks on the entire volume. + */ + size_t get_number_of_blocks() const { + return + number_of_blocks_ + // Size of the volume. + size_t(non_volume_blocks()); // Size of everything else. + } - /*! - Maps from a mass-storage device address to an address - in the underlying volume. - */ - ssize_t to_source_address(size_t address) const { - // The embedded volume is always the last thing on the device. - return ssize_t(address) - ssize_t(non_volume_blocks()); - } + /*! + Maps from a mass-storage device address to an address + in the underlying volume. + */ + ssize_t to_source_address(size_t address) const { + // The embedded volume is always the last thing on the device. + return ssize_t(address) - ssize_t(non_volume_blocks()); + } - /*! - Converts from a source data block to one properly encoded for the drive type. + /*! + Converts from a source data block to one properly encoded for the drive type. - Expected usage: + Expected usage: - const size_t source_address = mapper.to_source_address(unit_address); - if(is_in_range_for_partition(source_address)) { - return mapper.convert_source_block(source_address, get_block_contents(source_address)); - } else { - return mapper.convert_source_block(source_address); - } - */ - std::vector convert_source_block(ssize_t source_address, std::vector source_data = {}) { - // Addresses greater than or equal to zero map to the actual disk image. - if(source_address >= 0) return source_data; - - // Switch to mapping relative to 0, for personal sanity. - source_address += non_volume_blocks(); - - // Block 0 is the device descriptor, which lists the total number of blocks, - // and provides an offset to the driver, if any. - if(!source_address) { - const uint32_t total_device_blocks = uint32_t(get_number_of_blocks()); - const auto driver_size = uint16_t(driver_block_size()); - const auto driver_offset = uint16_t(predriver_blocks()); - const uint8_t driver_count = driver_size > 0 ? 1 : 0; - - /* The driver descriptor. */ - std::vector driver_description = { - 0x45, 0x52, /* device signature */ - 0x02, 0x00, /* block size, in bytes */ - - uint8_t(total_device_blocks >> 24), - uint8_t(total_device_blocks >> 16), - uint8_t(total_device_blocks >> 8), - uint8_t(total_device_blocks), - /* number of blocks on device */ - - 0x00, 0x01, /* reserved (formerly: device type) */ - 0x00, 0x01, /* reserved (formerly: device ID) */ - 0x00, 0x00, - 0x00, 0x00, /* reserved ('sbData', no further explanation given) */ - - 0x00, driver_count, /* number of device descriptor entries */ - 0x00, 0x00, - - /* first device descriptor's starting block */ - uint8_t(driver_offset >> 8), uint8_t(driver_offset), - - /* size of device driver */ - uint8_t(driver_size >> 8), uint8_t(driver_size), - - 0x00, 0x01, /* - More modern documentation: operating system (MacOS = 1) - Inside Macintosh IV: system type (Mac Plus = 1) - */ - }; - driver_description.resize(512); - - return driver_description; - } - - // Blocks 1 and 2 contain entries of the partition map; there's also possibly an entry - // for the driver. - if(source_address < 3 + volume_provider_.HasDriver) { - struct Partition { - const char *name, *type; - uint32_t start_block, size; - uint8_t status; - } partitions[3] = { - { - volume_provider_.name(), - volume_provider_.type(), - uint32_t(non_volume_blocks()), - uint32_t(number_of_blocks_), - 0xb7 - }, - { - "Apple", - "Apple_partition_map", - 0x01, - uint32_t(predriver_blocks()) - 1, - 0x37 - }, - { - "Macintosh", - "Apple_Driver", - uint32_t(predriver_blocks()), - uint32_t(driver_block_size()), - 0x7f - }, - }; - - std::vector partition(512); - - // Fill in the fixed fields. - partition[0] = 'P'; partition[1] = 'M'; /* Signature. */ - partition[7] = 3; /* Number of partitions. */ - - const Partition &details = partitions[source_address-1]; - - partition[8] = uint8_t(details.start_block >> 24); - partition[9] = uint8_t(details.start_block >> 16); - partition[10] = uint8_t(details.start_block >> 8); - partition[11] = uint8_t(details.start_block); - - partition[84] = partition[12] = uint8_t(details.size >> 24); - partition[85] = partition[13] = uint8_t(details.size >> 16); - partition[86] = partition[14] = uint8_t(details.size >> 8); - partition[87] = partition[15] = uint8_t(details.size); - - // 32 bytes are allocated for each of the following strings. - memcpy(&partition[16], details.name, strlen(details.name)); - memcpy(&partition[48], details.type, strlen(details.type)); - - partition[91] = details.status; - - // The third entry in this constructed partition map is the driver; - // add some additional details. - if constexpr (VolumeProvider::HasDriver) { - if(source_address == 3) { - const auto driver_size = uint16_t(volume_provider_.driver_size()); - const auto driver_checksum = uint16_t(volume_provider_.driver_checksum()); - - /* Driver size in bytes. */ - partition[98] = uint8_t(driver_size >> 8); - partition[99] = uint8_t(driver_size); - - /* Driver checksum. */ - partition[118] = uint8_t(driver_checksum >> 8); - partition[119] = uint8_t(driver_checksum); - - /* Driver target processor. */ - const char *driver_target = volume_provider_.driver_target(); - memcpy(&partition[120], driver_target, strlen(driver_target)); - - // Various non-zero values that Apple HD SC Tool wrote are below; they are - // documented as reserved officially, so I don't know their meaning. - partition[137] = 0x01; - partition[138] = 0x06; - partition[143] = 0x01; - partition[147] = 0x02; - partition[149] = 0x07; - } - } - - return partition; - } - - if constexpr (VolumeProvider::HasDriver) { - // The remainder of the non-volume area is the driver. - if(source_address >= predriver_blocks() && source_address < non_volume_blocks()) { - const uint8_t *driver = volume_provider_.driver(); - const auto offset = (source_address - predriver_blocks()) * 512; - return std::vector(&driver[offset], &driver[offset + 512]); - } - } - - // Default: return an empty block. - return std::vector(512); - } - - private: - DriveType drive_type_; - size_t number_of_blocks_; - - VolumeProvider volume_provider_; - - ssize_t predriver_blocks() const { - return - 0x40; // Holding: - // (i) the driver descriptor; - // (ii) the partition table; and - // (iii) the partition entries. - } - - ssize_t non_volume_blocks() const { - return - predriver_blocks() + - driver_block_size(); // Size of device driver (if any). - } - - ssize_t driver_block_size() const { - if constexpr (VolumeProvider::HasDriver) { - return (volume_provider_.driver_size() + 511) >> 9; + const size_t source_address = mapper.to_source_address(unit_address); + if(is_in_range_for_partition(source_address)) { + return mapper.convert_source_block(source_address, get_block_contents(source_address)); } else { - return 0; + return mapper.convert_source_block(source_address); + } + */ + std::vector convert_source_block(ssize_t source_address, std::vector source_data = {}) { + // Addresses greater than or equal to zero map to the actual disk image. + if(source_address >= 0) return source_data; + + // Switch to mapping relative to 0, for personal sanity. + source_address += non_volume_blocks(); + + // Block 0 is the device descriptor, which lists the total number of blocks, + // and provides an offset to the driver, if any. + if(!source_address) { + const uint32_t total_device_blocks = uint32_t(get_number_of_blocks()); + const auto driver_size = uint16_t(driver_block_size()); + const auto driver_offset = uint16_t(predriver_blocks()); + const uint8_t driver_count = driver_size > 0 ? 1 : 0; + + /* The driver descriptor. */ + std::vector driver_description = { + 0x45, 0x52, /* device signature */ + 0x02, 0x00, /* block size, in bytes */ + + uint8_t(total_device_blocks >> 24), + uint8_t(total_device_blocks >> 16), + uint8_t(total_device_blocks >> 8), + uint8_t(total_device_blocks), + /* number of blocks on device */ + + 0x00, 0x01, /* reserved (formerly: device type) */ + 0x00, 0x01, /* reserved (formerly: device ID) */ + 0x00, 0x00, + 0x00, 0x00, /* reserved ('sbData', no further explanation given) */ + + 0x00, driver_count, /* number of device descriptor entries */ + 0x00, 0x00, + + /* first device descriptor's starting block */ + uint8_t(driver_offset >> 8), uint8_t(driver_offset), + + /* size of device driver */ + uint8_t(driver_size >> 8), uint8_t(driver_size), + + 0x00, 0x01, /* + More modern documentation: operating system (MacOS = 1) + Inside Macintosh IV: system type (Mac Plus = 1) + */ + }; + driver_description.resize(512); + + return driver_description; + } + + // Blocks 1 and 2 contain entries of the partition map; there's also possibly an entry + // for the driver. + if(source_address < 3 + volume_provider_.HasDriver) { + struct Partition { + const char *name, *type; + uint32_t start_block, size; + uint8_t status; + } partitions[3] = { + { + volume_provider_.name(), + volume_provider_.type(), + uint32_t(non_volume_blocks()), + uint32_t(number_of_blocks_), + 0xb7 + }, + { + "Apple", + "Apple_partition_map", + 0x01, + uint32_t(predriver_blocks()) - 1, + 0x37 + }, + { + "Macintosh", + "Apple_Driver", + uint32_t(predriver_blocks()), + uint32_t(driver_block_size()), + 0x7f + }, + }; + + std::vector partition(512); + + // Fill in the fixed fields. + partition[0] = 'P'; partition[1] = 'M'; /* Signature. */ + partition[7] = 3; /* Number of partitions. */ + + const Partition &details = partitions[source_address-1]; + + partition[8] = uint8_t(details.start_block >> 24); + partition[9] = uint8_t(details.start_block >> 16); + partition[10] = uint8_t(details.start_block >> 8); + partition[11] = uint8_t(details.start_block); + + partition[84] = partition[12] = uint8_t(details.size >> 24); + partition[85] = partition[13] = uint8_t(details.size >> 16); + partition[86] = partition[14] = uint8_t(details.size >> 8); + partition[87] = partition[15] = uint8_t(details.size); + + // 32 bytes are allocated for each of the following strings. + memcpy(&partition[16], details.name, strlen(details.name)); + memcpy(&partition[48], details.type, strlen(details.type)); + + partition[91] = details.status; + + // The third entry in this constructed partition map is the driver; + // add some additional details. + if constexpr (VolumeProvider::HasDriver) { + if(source_address == 3) { + const auto driver_size = uint16_t(volume_provider_.driver_size()); + const auto driver_checksum = uint16_t(volume_provider_.driver_checksum()); + + /* Driver size in bytes. */ + partition[98] = uint8_t(driver_size >> 8); + partition[99] = uint8_t(driver_size); + + /* Driver checksum. */ + partition[118] = uint8_t(driver_checksum >> 8); + partition[119] = uint8_t(driver_checksum); + + /* Driver target processor. */ + const char *driver_target = volume_provider_.driver_target(); + memcpy(&partition[120], driver_target, strlen(driver_target)); + + // Various non-zero values that Apple HD SC Tool wrote are below; they are + // documented as reserved officially, so I don't know their meaning. + partition[137] = 0x01; + partition[138] = 0x06; + partition[143] = 0x01; + partition[147] = 0x02; + partition[149] = 0x07; + } + } + + return partition; + } + + if constexpr (VolumeProvider::HasDriver) { + // The remainder of the non-volume area is the driver. + if(source_address >= predriver_blocks() && source_address < non_volume_blocks()) { + const uint8_t *driver = volume_provider_.driver(); + const auto offset = (source_address - predriver_blocks()) * 512; + return std::vector(&driver[offset], &driver[offset + 512]); } } + + // Default: return an empty block. + return std::vector(512); + } + +private: + DriveType drive_type_; + size_t number_of_blocks_; + + VolumeProvider volume_provider_; + + ssize_t predriver_blocks() const { + return + 0x40; // Holding: + // (i) the driver descriptor; + // (ii) the partition table; and + // (iii) the partition entries. + } + + ssize_t non_volume_blocks() const { + return + predriver_blocks() + + driver_block_size(); // Size of device driver (if any). + } + + ssize_t driver_block_size() const { + if constexpr (VolumeProvider::HasDriver) { + return (volume_provider_.driver_size() + 511) >> 9; + } else { + return 0; + } + } }; } diff --git a/Storage/MassStorage/Encodings/MacintoshVolume.hpp b/Storage/MassStorage/Encodings/MacintoshVolume.hpp index b53f8feab..6161e2b2a 100644 --- a/Storage/MassStorage/Encodings/MacintoshVolume.hpp +++ b/Storage/MassStorage/Encodings/MacintoshVolume.hpp @@ -30,8 +30,8 @@ using DriveType = Storage::MassStorage::Encodings::Apple::DriveType; impersonate different types of Macintosh drive. */ class Volume { - public: - virtual void set_drive_type(DriveType type) = 0; +public: + virtual void set_drive_type(DriveType type) = 0; }; struct VolumeProvider { diff --git a/Storage/MassStorage/Formats/DAT.hpp b/Storage/MassStorage/Formats/DAT.hpp index a88cb9ab2..d3464b496 100644 --- a/Storage/MassStorage/Formats/DAT.hpp +++ b/Storage/MassStorage/Formats/DAT.hpp @@ -18,8 +18,8 @@ namespace Storage::MassStorage { in 256-byte blocks. */ class DAT: public RawSectorDump<256> { - public: - DAT(const std::string &file_name); +public: + DAT(const std::string &file_name); }; } diff --git a/Storage/MassStorage/Formats/DSK.hpp b/Storage/MassStorage/Formats/DSK.hpp index f0c182bc6..e31b947d8 100644 --- a/Storage/MassStorage/Formats/DSK.hpp +++ b/Storage/MassStorage/Formats/DSK.hpp @@ -18,8 +18,8 @@ namespace Storage::MassStorage { in 512-byte blocks. */ class DSK: public RawSectorDump<512> { - public: - DSK(const std::string &file_name); +public: + DSK(const std::string &file_name); }; } diff --git a/Storage/MassStorage/Formats/HDV.hpp b/Storage/MassStorage/Formats/HDV.hpp index 0f02a9a79..99499420d 100644 --- a/Storage/MassStorage/Formats/HDV.hpp +++ b/Storage/MassStorage/Formats/HDV.hpp @@ -22,30 +22,30 @@ namespace Storage::MassStorage { the ProDOS volume of an Apple II drive. */ class HDV: public MassStorageDevice { - public: - /*! - Constructs an HDV with the contents of the file named @c file_name within - the range given. +public: + /*! + Constructs an HDV with the contents of the file named @c file_name within + the range given. - Raises an exception if the file name doesn't appear to identify a valid - Apple II mass storage image. - */ - HDV(const std::string &file_name, long start = 0, long size = std::numeric_limits::max()); + Raises an exception if the file name doesn't appear to identify a valid + Apple II mass storage image. + */ + HDV(const std::string &file_name, long start = 0, long size = std::numeric_limits::max()); - private: - FileHolder file_; - long file_start_, image_size_; - Storage::MassStorage::Encodings::AppleII::Mapper mapper_; +private: + FileHolder file_; + long file_start_, image_size_; + Storage::MassStorage::Encodings::AppleII::Mapper mapper_; - /// @returns -1 if @c address is out of range; the offset into the file at which - /// the block for @c address resides otherwise. - long offset_for_block(ssize_t address); + /// @returns -1 if @c address is out of range; the offset into the file at which + /// the block for @c address resides otherwise. + long offset_for_block(ssize_t address); - /* MassStorageDevices overrides. */ - size_t get_block_size() final; - size_t get_number_of_blocks() final; - std::vector get_block(size_t address) final; - void set_block(size_t address, const std::vector &) final; + /* MassStorageDevices overrides. */ + size_t get_block_size() final; + size_t get_number_of_blocks() final; + std::vector get_block(size_t address) final; + void set_block(size_t address, const std::vector &) final; }; } diff --git a/Storage/MassStorage/Formats/HFV.hpp b/Storage/MassStorage/Formats/HFV.hpp index dea5886e6..af68879ad 100644 --- a/Storage/MassStorage/Formats/HFV.hpp +++ b/Storage/MassStorage/Formats/HFV.hpp @@ -22,28 +22,28 @@ namespace Storage::MassStorage { the HFS volume of a Macintosh drive that is not the correct size to be a floppy disk. */ class HFV: public MassStorageDevice, public Encodings::Macintosh::Volume { - public: - /*! - Constructs an HFV with the contents of the file named @c file_name. - Raises an exception if the file name doesn't appear to identify a valid - Macintosh mass storage image. - */ - HFV(const std::string &file_name); +public: + /*! + Constructs an HFV with the contents of the file named @c file_name. + Raises an exception if the file name doesn't appear to identify a valid + Macintosh mass storage image. + */ + HFV(const std::string &file_name); - private: - FileHolder file_; - Encodings::Macintosh::Mapper mapper_; +private: + FileHolder file_; + Encodings::Macintosh::Mapper mapper_; - /* MassStorageDevices overrides. */ - size_t get_block_size() final; - size_t get_number_of_blocks() final; - std::vector get_block(size_t address) final; - void set_block(size_t address, const std::vector &) final; + /* MassStorageDevices overrides. */ + size_t get_block_size() final; + size_t get_number_of_blocks() final; + std::vector get_block(size_t address) final; + void set_block(size_t address, const std::vector &) final; - /* Encodings::Macintosh::Volume overrides. */ - void set_drive_type(Encodings::Macintosh::DriveType) final; + /* Encodings::Macintosh::Volume overrides. */ + void set_drive_type(Encodings::Macintosh::DriveType) final; - std::map> writes_; + std::map> writes_; }; } diff --git a/Storage/MassStorage/Formats/RawSectorDump.hpp b/Storage/MassStorage/Formats/RawSectorDump.hpp index 463c65cb9..a9f5bf129 100644 --- a/Storage/MassStorage/Formats/RawSectorDump.hpp +++ b/Storage/MassStorage/Formats/RawSectorDump.hpp @@ -16,39 +16,39 @@ namespace Storage::MassStorage { template class RawSectorDump: public MassStorageDevice { - public: - RawSectorDump(const std::string &file_name, long offset = 0, long length = -1) : - file_(file_name), - file_size_((length == -1) ? long(file_.stats().st_size) : length), - file_start_(offset) - { - // Is the file a multiple of sector_size bytes in size? - if(file_size_ % sector_size) throw std::exception(); - } +public: + RawSectorDump(const std::string &file_name, long offset = 0, long length = -1) : + file_(file_name), + file_size_((length == -1) ? long(file_.stats().st_size) : length), + file_start_(offset) + { + // Is the file a multiple of sector_size bytes in size? + if(file_size_ % sector_size) throw std::exception(); + } - /* MassStorageDevices overrides. */ - size_t get_block_size() final { - return sector_size; - } + /* MassStorageDevices overrides. */ + size_t get_block_size() final { + return sector_size; + } - size_t get_number_of_blocks() final { - return size_t(file_size_ / sector_size); - } + size_t get_number_of_blocks() final { + return size_t(file_size_ / sector_size); + } - std::vector get_block(size_t address) final { - file_.seek(file_start_ + long(address * sector_size), SEEK_SET); - return file_.read(sector_size); - } + std::vector get_block(size_t address) final { + file_.seek(file_start_ + long(address * sector_size), SEEK_SET); + return file_.read(sector_size); + } - void set_block(size_t address, const std::vector &contents) final { - assert(contents.size() == sector_size); - file_.seek(file_start_ + long(address * sector_size), SEEK_SET); - file_.write(contents); - } + void set_block(size_t address, const std::vector &contents) final { + assert(contents.size() == sector_size); + file_.seek(file_start_ + long(address * sector_size), SEEK_SET); + file_.write(contents); + } - private: - FileHolder file_; - const long file_size_, file_start_; +private: + FileHolder file_; + const long file_size_, file_start_; }; } diff --git a/Storage/MassStorage/MassStorageDevice.hpp b/Storage/MassStorage/MassStorageDevice.hpp index 510b51c81..63ed4e7cb 100644 --- a/Storage/MassStorage/MassStorageDevice.hpp +++ b/Storage/MassStorage/MassStorageDevice.hpp @@ -26,31 +26,31 @@ namespace Storage::MassStorage { linearly-addressed store. */ class MassStorageDevice { - public: - virtual ~MassStorageDevice() = default; +public: + virtual ~MassStorageDevice() = default; - /*! - @returns The size of each individual block. - */ - virtual size_t get_block_size() = 0; + /*! + @returns The size of each individual block. + */ + virtual size_t get_block_size() = 0; - /*! - Block addresses run from 0 to n. The total number of blocks, n, - therefore provides the range of valid addresses. + /*! + Block addresses run from 0 to n. The total number of blocks, n, + therefore provides the range of valid addresses. - @returns The total number of blocks on the device. - */ - virtual size_t get_number_of_blocks() = 0; + @returns The total number of blocks on the device. + */ + virtual size_t get_number_of_blocks() = 0; - /*! - @returns The current contents of the block at @c address. - */ - virtual std::vector get_block(size_t address) = 0; + /*! + @returns The current contents of the block at @c address. + */ + virtual std::vector get_block(size_t address) = 0; - /*! - Sets new contents for the block at @c address. - */ - virtual void set_block([[maybe_unused]] size_t address, const std::vector &) {} + /*! + Sets new contents for the block at @c address. + */ + virtual void set_block([[maybe_unused]] size_t address, const std::vector &) {} }; } diff --git a/Storage/MassStorage/SCSI/DirectAccessDevice.hpp b/Storage/MassStorage/SCSI/DirectAccessDevice.hpp index 7b2f54c22..cad7b93a5 100644 --- a/Storage/MassStorage/SCSI/DirectAccessDevice.hpp +++ b/Storage/MassStorage/SCSI/DirectAccessDevice.hpp @@ -16,22 +16,22 @@ namespace SCSI { class DirectAccessDevice: public Target::Executor { - public: +public: - /*! - Sets the backing storage exposed by this direct-access device. - */ - void set_storage(const std::shared_ptr &device); + /*! + Sets the backing storage exposed by this direct-access device. + */ + void set_storage(const std::shared_ptr &device); - /* SCSI commands. */ - bool read(const Target::CommandState &, Target::Responder &); - bool write(const Target::CommandState &, Target::Responder &); - Inquiry inquiry_values(); - bool read_capacity(const Target::CommandState &, Target::Responder &); - bool format_unit(const Target::CommandState &, Target::Responder &); + /* SCSI commands. */ + bool read(const Target::CommandState &, Target::Responder &); + bool write(const Target::CommandState &, Target::Responder &); + Inquiry inquiry_values(); + bool read_capacity(const Target::CommandState &, Target::Responder &); + bool format_unit(const Target::CommandState &, Target::Responder &); - private: - std::shared_ptr device_; +private: + std::shared_ptr device_; }; } diff --git a/Storage/MassStorage/SCSI/SCSI.hpp b/Storage/MassStorage/SCSI/SCSI.hpp index de9336d5e..3d1c5d8e6 100644 --- a/Storage/MassStorage/SCSI/SCSI.hpp +++ b/Storage/MassStorage/SCSI/SCSI.hpp @@ -102,67 +102,67 @@ constexpr uint8_t data_lines(BusState state) { #undef us class Bus: public ClockingHint::Source, public Activity::Source { - public: - Bus(HalfCycles clock_rate); +public: + Bus(HalfCycles clock_rate); - /*! - Adds a device to the bus, returning the index it should use - to refer to itself in subsequent calls to set_device_output. - */ - size_t add_device(); + /*! + Adds a device to the bus, returning the index it should use + to refer to itself in subsequent calls to set_device_output. + */ + size_t add_device(); - /*! - Sets the current output for @c device. - */ - void set_device_output(size_t device, BusState output); + /*! + Sets the current output for @c device. + */ + void set_device_output(size_t device, BusState output); - /*! - @returns the current state of the bus. - */ - BusState get_state() const; + /*! + @returns the current state of the bus. + */ + BusState get_state() const; - struct Observer { - /// Reports to an observer that the bus changed from a previous state to @c new_state, - /// along with the time since that change was observed. The time is in seconds, and is - /// intended for comparison with the various constants defined at namespace scope: - /// ArbitrationDelay et al. Observers will be notified each time one of the thresholds - /// defined by those constants is crossed. - virtual void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) = 0; - }; - /*! - Adds an observer. - */ - void add_observer(Observer *); + struct Observer { + /// Reports to an observer that the bus changed from a previous state to @c new_state, + /// along with the time since that change was observed. The time is in seconds, and is + /// intended for comparison with the various constants defined at namespace scope: + /// ArbitrationDelay et al. Observers will be notified each time one of the thresholds + /// defined by those constants is crossed. + virtual void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) = 0; + }; + /*! + Adds an observer. + */ + void add_observer(Observer *); - /*! - SCSI buses don't have a clock. But devices on the bus are concerned with time-based factors, - and `run_for` is the way that time propagates within this emulator. So please permit this - fiction. - */ - void run_for(HalfCycles); + /*! + SCSI buses don't have a clock. But devices on the bus are concerned with time-based factors, + and `run_for` is the way that time propagates within this emulator. So please permit this + fiction. + */ + void run_for(HalfCycles); - /*! - Forces a `scsi_bus_did_change` propagation now. - */ - void update_observers(); + /*! + Forces a `scsi_bus_did_change` propagation now. + */ + void update_observers(); - // As per ClockingHint::Source. - ClockingHint::Preference preferred_clocking() const final; + // As per ClockingHint::Source. + ClockingHint::Preference preferred_clocking() const final; - // Fulfilling public Activity::Source. - void set_activity_observer(Activity::Observer *observer) final; + // Fulfilling public Activity::Source. + void set_activity_observer(Activity::Observer *observer) final; - private: - HalfCycles time_in_state_; - double cycles_to_time_ = 1.0; - size_t dispatch_index_ = 0; - std::array dispatch_times_; +private: + HalfCycles time_in_state_; + double cycles_to_time_ = 1.0; + size_t dispatch_index_ = 0; + std::array dispatch_times_; - std::vector device_states_; - BusState state_ = DefaultBusState; - std::vector observers_; + std::vector device_states_; + BusState state_ = DefaultBusState; + std::vector observers_; - Activity::Observer *activity_observer_ = nullptr; + Activity::Observer *activity_observer_ = nullptr; }; } diff --git a/Storage/MassStorage/SCSI/Target.hpp b/Storage/MassStorage/SCSI/Target.hpp index 1daee626d..4afa4307c 100644 --- a/Storage/MassStorage/SCSI/Target.hpp +++ b/Storage/MassStorage/SCSI/Target.hpp @@ -22,63 +22,63 @@ namespace SCSI::Target { the command phase plus any other data read since then. */ class CommandState { - public: - CommandState(const std::vector &command, const std::vector &received); +public: + CommandState(const std::vector &command, const std::vector &received); - // For read and write commands. - struct ReadWrite { - uint32_t address, number_of_blocks; - }; - ReadWrite read_write_specs() const; + // For read and write commands. + struct ReadWrite { + uint32_t address, number_of_blocks; + }; + ReadWrite read_write_specs() const; - // For inquiry commands. - size_t allocated_inquiry_bytes() const; + // For inquiry commands. + size_t allocated_inquiry_bytes() const; - // For mode sense commands. - struct ModeSense { - bool exclude_block_descriptors = false; - enum class PageControlValues { - Current = 0, - Changeable = 1, - Default = 2, - Saved = 3 - } page_control_values = PageControlValues::Current; - uint8_t page_code; - uint8_t subpage_code; - uint16_t allocated_bytes; - }; - ModeSense mode_sense_specs() const; + // For mode sense commands. + struct ModeSense { + bool exclude_block_descriptors = false; + enum class PageControlValues { + Current = 0, + Changeable = 1, + Default = 2, + Saved = 3 + } page_control_values = PageControlValues::Current; + uint8_t page_code; + uint8_t subpage_code; + uint16_t allocated_bytes; + }; + ModeSense mode_sense_specs() const; - struct ModeSelect { - bool content_is_vendor_specific = true; - bool revert_to_default = false; - bool save_pages = false; - uint16_t parameter_list_length = 0; - }; - ModeSelect mode_select_specs() const; + struct ModeSelect { + bool content_is_vendor_specific = true; + bool revert_to_default = false; + bool save_pages = false; + uint16_t parameter_list_length = 0; + }; + ModeSelect mode_select_specs() const; - struct ReadBuffer { - enum class Mode { - CombinedHeaderAndData = 0, - VendorSpecific = 1, - Data = 2, - Descriptor = 3, - Reserved = 4 - } mode = Mode::CombinedHeaderAndData; - uint8_t buffer_id = 0; - uint32_t buffer_offset = 0, buffer_length = 0; - }; - ReadBuffer read_buffer_specs() const; + struct ReadBuffer { + enum class Mode { + CombinedHeaderAndData = 0, + VendorSpecific = 1, + Data = 2, + Descriptor = 3, + Reserved = 4 + } mode = Mode::CombinedHeaderAndData; + uint8_t buffer_id = 0; + uint32_t buffer_offset = 0, buffer_length = 0; + }; + ReadBuffer read_buffer_specs() const; - const std::vector &received_data() const { - return received_; - } + const std::vector &received_data() const { + return received_; + } - private: - uint32_t address() const; - uint16_t number_of_blocks() const; - const std::vector &data_; - const std::vector &received_; +private: + uint32_t address() const; + uint16_t number_of_blocks() const; + const std::vector &data_; + const std::vector &received_; }; /*! @@ -332,65 +332,65 @@ struct Executor { as Executors. */ template class Target: public Bus::Observer, public Responder { - public: - /*! - Instantiates a target attached to @c bus, - with SCSI ID @c scsi_id — a number in the range 0 to 7. +public: + /*! + Instantiates a target attached to @c bus, + with SCSI ID @c scsi_id — a number in the range 0 to 7. - Received commands will be handed to the Executor to perform. - */ - Target(Bus &bus, int scsi_id); + Received commands will be handed to the Executor to perform. + */ + Target(Bus &bus, int scsi_id); - inline Executor *operator->() { - return &executor_; - } + inline Executor *operator->() { + return &executor_; + } - private: - Executor executor_; - Log::Logger log_; +private: + Executor executor_; + Log::Logger log_; - // Bus::Observer. - void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) final; + // Bus::Observer. + void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) final; - // Responder - void send_data(std::vector &&data, continuation next) final; - void receive_data(size_t length, continuation next) final; - void send_status(Status, continuation next) final; - void send_message(Message, continuation next) final; - void end_command() final; + // Responder + void send_data(std::vector &&data, continuation next) final; + void receive_data(size_t length, continuation next) final; + void send_status(Status, continuation next) final; + void send_message(Message, continuation next) final; + void end_command() final; - // Instance storage. - Bus &bus_; - const BusState scsi_id_mask_; - const size_t scsi_bus_device_id_; + // Instance storage. + Bus &bus_; + const BusState scsi_id_mask_; + const size_t scsi_bus_device_id_; - enum class Phase { - AwaitingSelection, - Command, - ReceivingData, - SendingData, - SendingStatus, - SendingMessage - } phase_ = Phase::AwaitingSelection; - BusState bus_state_ = DefaultBusState; + enum class Phase { + AwaitingSelection, + Command, + ReceivingData, + SendingData, + SendingStatus, + SendingMessage + } phase_ = Phase::AwaitingSelection; + BusState bus_state_ = DefaultBusState; - void set_device_output(BusState state) { - expected_control_state_ = state & (Line::Control | Line::Input | Line::Message); - bus_.set_device_output(scsi_bus_device_id_, state); - } - BusState expected_control_state_ = DefaultBusState; + void set_device_output(BusState state) { + expected_control_state_ = state & (Line::Control | Line::Input | Line::Message); + bus_.set_device_output(scsi_bus_device_id_, state); + } + BusState expected_control_state_ = DefaultBusState; - void begin_command(uint8_t first_byte); - std::vector command_; - Status status_; - Message message_; - size_t command_pointer_ = 0; - bool dispatch_command(); + void begin_command(uint8_t first_byte); + std::vector command_; + Status status_; + Message message_; + size_t command_pointer_ = 0; + bool dispatch_command(); - std::vector data_; - size_t data_pointer_ = 0; + std::vector data_; + size_t data_pointer_ = 0; - continuation next_function_; + continuation next_function_; }; #include "TargetImplementation.hpp" diff --git a/Storage/Storage.hpp b/Storage/Storage.hpp index 26a397426..bb06464f5 100644 --- a/Storage/Storage.hpp +++ b/Storage/Storage.hpp @@ -206,90 +206,90 @@ struct Time { return Time(std::numeric_limits::max()); } - private: - inline void install_result(uint64_t long_length, uint64_t long_clock_rate) { - if(long_length <= std::numeric_limits::max() && long_clock_rate <= std::numeric_limits::max()) { - length = unsigned(long_length); - clock_rate = unsigned(long_clock_rate); - return; - } +private: + inline void install_result(uint64_t long_length, uint64_t long_clock_rate) { + if(long_length <= std::numeric_limits::max() && long_clock_rate <= std::numeric_limits::max()) { + length = unsigned(long_length); + clock_rate = unsigned(long_clock_rate); + return; + } - // TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy. - if(!long_length) { - length = 0; - clock_rate = 1; - return; - } + // TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy. + if(!long_length) { + length = 0; + clock_rate = 1; + return; + } - while(!(long_length&0xf) && !(long_clock_rate&0xf)) { - long_length >>= 4; - long_clock_rate >>= 4; - } + while(!(long_length&0xf) && !(long_clock_rate&0xf)) { + long_length >>= 4; + long_clock_rate >>= 4; + } - while(!(long_length&1) && !(long_clock_rate&1)) { + while(!(long_length&1) && !(long_clock_rate&1)) { + long_length >>= 1; + long_clock_rate >>= 1; + } + + if(long_length > std::numeric_limits::max() || long_clock_rate > std::numeric_limits::max()) { + uint64_t common_divisor = std::gcd(long_length, long_clock_rate); + long_length /= common_divisor; + long_clock_rate /= common_divisor; + + // Okay, in desperation accept a loss of accuracy. + while( + (long_length > std::numeric_limits::max() || long_clock_rate > std::numeric_limits::max()) && + (long_clock_rate > 1)) { long_length >>= 1; long_clock_rate >>= 1; } - - if(long_length > std::numeric_limits::max() || long_clock_rate > std::numeric_limits::max()) { - uint64_t common_divisor = std::gcd(long_length, long_clock_rate); - long_length /= common_divisor; - long_clock_rate /= common_divisor; - - // Okay, in desperation accept a loss of accuracy. - while( - (long_length > std::numeric_limits::max() || long_clock_rate > std::numeric_limits::max()) && - (long_clock_rate > 1)) { - long_length >>= 1; - long_clock_rate >>= 1; - } - } - - if(long_length <= std::numeric_limits::max() && long_clock_rate <= std::numeric_limits::max()) { - length = unsigned(long_length); - clock_rate = unsigned(long_clock_rate); - } else { - length = std::numeric_limits::max(); - clock_rate = 1u; - } } - inline void install_float(float value) { - // Grab the float's native mantissa and exponent. - int exponent; - const float mantissa = frexpf(value, &exponent); - - // Turn the mantissa into an int, and adjust the exponent - // appropriately. - const uint64_t loaded_mantissa = uint64_t(ldexpf(mantissa, 24)); - const auto relative_exponent = exponent - 24; - - // If the mantissa is negative and its absolute value fits within a 64-bit integer, - // just load up. - if(relative_exponent <= 0 && relative_exponent > -64) { - install_result(loaded_mantissa, uint64_t(1) << -relative_exponent); - return; - } - - // If the exponent is positive but doesn't cause loaded_mantissa to overflow, - // install with the natural encoding. - if(relative_exponent > 0 && relative_exponent < (64 - 24)) { - install_result(loaded_mantissa << relative_exponent, 1); - return; - } - - // Otherwise, if this number is too large to store, store the maximum value. - if(relative_exponent > 0) { - install_result(std::numeric_limits::max(), 1); - return; - } - - // If the number is too small to store accurately, store 0. - if(relative_exponent < 0) { - install_result(0, 1); - return; - } + if(long_length <= std::numeric_limits::max() && long_clock_rate <= std::numeric_limits::max()) { + length = unsigned(long_length); + clock_rate = unsigned(long_clock_rate); + } else { + length = std::numeric_limits::max(); + clock_rate = 1u; } + } + + inline void install_float(float value) { + // Grab the float's native mantissa and exponent. + int exponent; + const float mantissa = frexpf(value, &exponent); + + // Turn the mantissa into an int, and adjust the exponent + // appropriately. + const uint64_t loaded_mantissa = uint64_t(ldexpf(mantissa, 24)); + const auto relative_exponent = exponent - 24; + + // If the mantissa is negative and its absolute value fits within a 64-bit integer, + // just load up. + if(relative_exponent <= 0 && relative_exponent > -64) { + install_result(loaded_mantissa, uint64_t(1) << -relative_exponent); + return; + } + + // If the exponent is positive but doesn't cause loaded_mantissa to overflow, + // install with the natural encoding. + if(relative_exponent > 0 && relative_exponent < (64 - 24)) { + install_result(loaded_mantissa << relative_exponent, 1); + return; + } + + // Otherwise, if this number is too large to store, store the maximum value. + if(relative_exponent > 0) { + install_result(std::numeric_limits::max(), 1); + return; + } + + // If the number is too small to store accurately, store 0. + if(relative_exponent < 0) { + install_result(0, 1); + return; + } + } }; } diff --git a/Storage/Tape/Formats/CAS.hpp b/Storage/Tape/Formats/CAS.hpp index 37ae4a930..61fe55858 100644 --- a/Storage/Tape/Formats/CAS.hpp +++ b/Storage/Tape/Formats/CAS.hpp @@ -21,48 +21,48 @@ namespace Storage::Tape { Provides a @c Tape containing a CAS tape image, which is an MSX byte stream. */ class CAS: public Tape { - public: - /*! - Constructs a @c CAS containing content from the file with name @c file_name. +public: + /*! + Constructs a @c CAS containing content from the file with name @c file_name. - @throws ErrorNotCAS if this file could not be opened and recognised as a valid CAS file. - */ - CAS(const std::string &file_name); + @throws ErrorNotCAS if this file could not be opened and recognised as a valid CAS file. + */ + CAS(const std::string &file_name); - enum { - ErrorNotCAS - }; + enum { + ErrorNotCAS + }; - // implemented to satisfy @c Tape - bool is_at_end(); + // implemented to satisfy @c Tape + bool is_at_end(); - private: - void virtual_reset(); - Pulse virtual_get_next_pulse(); +private: + void virtual_reset(); + Pulse virtual_get_next_pulse(); - // Storage for the array of data blobs to transcribe into audio; - // each chunk is preceded by a header which may be long, and is optionally - // also preceded by a gap. - struct Chunk { - bool has_gap; - bool long_header; - std::vector data; + // Storage for the array of data blobs to transcribe into audio; + // each chunk is preceded by a header which may be long, and is optionally + // also preceded by a gap. + struct Chunk { + bool has_gap; + bool long_header; + std::vector data; - Chunk(bool has_gap, bool long_header, const std::vector &data) : - has_gap(has_gap), long_header(long_header), data(std::move(data)) {} - }; - std::vector chunks_; + Chunk(bool has_gap, bool long_header, const std::vector &data) : + has_gap(has_gap), long_header(long_header), data(std::move(data)) {} + }; + std::vector chunks_; - // Tracker for active state within the file list. - std::size_t chunk_pointer_ = 0; - enum class Phase { - Header, - Bytes, - Gap, - EndOfFile - } phase_ = Phase::Header; - std::size_t distance_into_phase_ = 0; - std::size_t distance_into_bit_ = 0; + // Tracker for active state within the file list. + std::size_t chunk_pointer_ = 0; + enum class Phase { + Header, + Bytes, + Gap, + EndOfFile + } phase_ = Phase::Header; + std::size_t distance_into_phase_ = 0; + std::size_t distance_into_bit_ = 0; }; } diff --git a/Storage/Tape/Formats/CSW.hpp b/Storage/Tape/Formats/CSW.hpp index b3019a3d7..e269a3084 100644 --- a/Storage/Tape/Formats/CSW.hpp +++ b/Storage/Tape/Formats/CSW.hpp @@ -20,44 +20,44 @@ namespace Storage::Tape { Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. */ class CSW: public Tape { - public: - /*! - Constructs a @c CSW containing content from the file with name @c file_name. +public: + /*! + Constructs a @c CSW containing content from the file with name @c file_name. - @throws ErrorNotCSW if this file could not be opened and recognised as a valid CSW file. - */ - CSW(const std::string &file_name); + @throws ErrorNotCSW if this file could not be opened and recognised as a valid CSW file. + */ + CSW(const std::string &file_name); - enum class CompressionType { - RLE, - ZRLE - }; + enum class CompressionType { + RLE, + ZRLE + }; - /*! - Constructs a @c CSW containing content as specified. Does not throw. - */ - CSW(const std::vector &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate); + /*! + Constructs a @c CSW containing content as specified. Does not throw. + */ + CSW(const std::vector &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate); - enum { - ErrorNotCSW - }; + enum { + ErrorNotCSW + }; - // implemented to satisfy @c Tape - bool is_at_end(); + // implemented to satisfy @c Tape + bool is_at_end(); - private: - void virtual_reset(); - Pulse virtual_get_next_pulse(); +private: + void virtual_reset(); + Pulse virtual_get_next_pulse(); - Pulse pulse_; - CompressionType compression_type_; + Pulse pulse_; + CompressionType compression_type_; - uint8_t get_next_byte(); - uint32_t get_next_int32le(); - void invert_pulse(); + uint8_t get_next_byte(); + uint32_t get_next_int32le(); + void invert_pulse(); - std::vector source_data_; - std::size_t source_data_pointer_; + std::vector source_data_; + std::size_t source_data_pointer_; }; } diff --git a/Storage/Tape/Formats/CommodoreTAP.hpp b/Storage/Tape/Formats/CommodoreTAP.hpp index ffe692295..25bfede2b 100644 --- a/Storage/Tape/Formats/CommodoreTAP.hpp +++ b/Storage/Tape/Formats/CommodoreTAP.hpp @@ -20,31 +20,31 @@ namespace Storage::Tape { Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings. */ class CommodoreTAP: public Tape { - public: - /*! - Constructs a @c CommodoreTAP containing content from the file with name @c file_name. +public: + /*! + Constructs a @c CommodoreTAP containing content from the file with name @c file_name. - @throws ErrorNotCommodoreTAP if this file could not be opened and recognised as a valid Commodore-format TAP. - */ - CommodoreTAP(const std::string &file_name); + @throws ErrorNotCommodoreTAP if this file could not be opened and recognised as a valid Commodore-format TAP. + */ + CommodoreTAP(const std::string &file_name); - enum { - ErrorNotCommodoreTAP - }; + enum { + ErrorNotCommodoreTAP + }; - // implemented to satisfy @c Tape - bool is_at_end(); + // implemented to satisfy @c Tape + bool is_at_end(); - private: - Storage::FileHolder file_; - void virtual_reset(); - Pulse virtual_get_next_pulse(); +private: + Storage::FileHolder file_; + void virtual_reset(); + Pulse virtual_get_next_pulse(); - bool updated_layout_; - uint32_t file_size_; + bool updated_layout_; + uint32_t file_size_; - Pulse current_pulse_; - bool is_at_end_ = false; + Pulse current_pulse_; + bool is_at_end_ = false; }; } diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp index 3a9446768..3a996a6e0 100644 --- a/Storage/Tape/Formats/OricTAP.hpp +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -20,36 +20,36 @@ namespace Storage::Tape { Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture. */ class OricTAP: public Tape { - public: - /*! - Constructs an @c OricTAP containing content from the file with name @c file_name. +public: + /*! + Constructs an @c OricTAP containing content from the file with name @c file_name. - @throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP. - */ - OricTAP(const std::string &file_name); + @throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP. + */ + OricTAP(const std::string &file_name); - enum { - ErrorNotOricTAP - }; + enum { + ErrorNotOricTAP + }; - // implemented to satisfy @c Tape - bool is_at_end(); + // implemented to satisfy @c Tape + bool is_at_end(); - private: - Storage::FileHolder file_; - void virtual_reset(); - Pulse virtual_get_next_pulse(); +private: + Storage::FileHolder file_; + void virtual_reset(); + Pulse virtual_get_next_pulse(); - // byte serialisation and output - uint16_t current_value_; - int bit_count_; - int pulse_counter_; + // byte serialisation and output + uint16_t current_value_; + int bit_count_; + int pulse_counter_; - enum Phase { - LeadIn, Header, Data, Gap, End - } phase_, next_phase_; - int phase_counter_; - uint16_t data_end_address_, data_start_address_; + enum Phase { + LeadIn, Header, Data, Gap, End + } phase_, next_phase_; + int phase_counter_; + uint16_t data_end_address_, data_start_address_; }; } diff --git a/Storage/Tape/Formats/TZX.hpp b/Storage/Tape/Formats/TZX.hpp index 372fda876..a432d53c1 100644 --- a/Storage/Tape/Formats/TZX.hpp +++ b/Storage/Tape/Formats/TZX.hpp @@ -19,82 +19,82 @@ namespace Storage::Tape { Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. */ class TZX: public PulseQueuedTape { - public: - /*! - Constructs a @c TZX containing content from the file with name @c file_name. +public: + /*! + Constructs a @c TZX containing content from the file with name @c file_name. - @throws ErrorNotTZX if this file could not be opened and recognised as a valid TZX file. - */ - TZX(const std::string &file_name); + @throws ErrorNotTZX if this file could not be opened and recognised as a valid TZX file. + */ + TZX(const std::string &file_name); - enum { - ErrorNotTZX - }; + enum { + ErrorNotTZX + }; - private: - Storage::FileHolder file_; +private: + Storage::FileHolder file_; - void virtual_reset(); - void get_next_pulses(); + void virtual_reset(); + void get_next_pulses(); - bool current_level_; + bool current_level_; - void get_standard_speed_data_block(); - void get_turbo_speed_data_block(); - void get_pure_tone_data_block(); - void get_pulse_sequence(); - void get_pure_data_block(); - void get_direct_recording_block(); - void get_csw_recording_block(); - void get_generalised_data_block(); - void get_pause(); + void get_standard_speed_data_block(); + void get_turbo_speed_data_block(); + void get_pure_tone_data_block(); + void get_pulse_sequence(); + void get_pure_data_block(); + void get_direct_recording_block(); + void get_csw_recording_block(); + void get_generalised_data_block(); + void get_pause(); - void ignore_group_start(); - void ignore_group_end(); - void ignore_jump_to_block(); - void ignore_loop_start(); - void ignore_loop_end(); - void ignore_call_sequence(); - void ignore_return_from_sequence(); - void ignore_select_block(); - void ignore_stop_tape_if_in_48kb_mode(); + void ignore_group_start(); + void ignore_group_end(); + void ignore_jump_to_block(); + void ignore_loop_start(); + void ignore_loop_end(); + void ignore_call_sequence(); + void ignore_return_from_sequence(); + void ignore_select_block(); + void ignore_stop_tape_if_in_48kb_mode(); - void get_set_signal_level(); + void get_set_signal_level(); - void ignore_text_description(); - void ignore_message_block(); - void ignore_archive_info(); - void get_hardware_type(); - void ignore_custom_info_block(); + void ignore_text_description(); + void ignore_message_block(); + void ignore_archive_info(); + void get_hardware_type(); + void ignore_custom_info_block(); - void get_kansas_city_block(); - void ignore_glue_block(); + void get_kansas_city_block(); + void ignore_glue_block(); - struct Data { - unsigned int length_of_zero_bit_pulse; - unsigned int length_of_one_bit_pulse; - unsigned int number_of_bits_in_final_byte; - unsigned int pause_after_block; - uint32_t data_length; - }; + struct Data { + unsigned int length_of_zero_bit_pulse; + unsigned int length_of_one_bit_pulse; + unsigned int number_of_bits_in_final_byte; + unsigned int pause_after_block; + uint32_t data_length; + }; - struct DataBlock { - unsigned int length_of_pilot_pulse; - unsigned int length_of_sync_first_pulse; - unsigned int length_of_sync_second_pulse; - unsigned int length_of_pilot_tone; - Data data; - }; + struct DataBlock { + unsigned int length_of_pilot_pulse; + unsigned int length_of_sync_first_pulse; + unsigned int length_of_sync_second_pulse; + unsigned int length_of_pilot_tone; + Data data; + }; - void get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data); - void get_data_block(const DataBlock &); - void get_data(const Data &); + void get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data); + void get_data_block(const DataBlock &); + void get_data(const Data &); - void post_pulses(unsigned int count, unsigned int length); - void post_pulse(unsigned int length); - void post_gap(unsigned int milliseconds); + void post_pulses(unsigned int count, unsigned int length); + void post_pulse(unsigned int length); + void post_gap(unsigned int milliseconds); - void post_pulse(const Storage::Time &time); + void post_pulse(const Storage::Time &time); }; } diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp index 1df474fe5..0d0cd180e 100644 --- a/Storage/Tape/Formats/TapePRG.hpp +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -20,52 +20,52 @@ namespace Storage::Tape { Provides a @c Tape containing a .PRG, which is a direct local file. */ class PRG: public Tape { - public: - /*! - Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. +public: + /*! + Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. - @param file_name The name of the file to load. - @throws ErrorBadFormat if this file could not be opened and recognised as the specified type. - */ - PRG(const std::string &file_name); + @param file_name The name of the file to load. + @throws ErrorBadFormat if this file could not be opened and recognised as the specified type. + */ + PRG(const std::string &file_name); - enum { - ErrorBadFormat - }; + enum { + ErrorBadFormat + }; - // implemented to satisfy @c Tape - bool is_at_end(); + // implemented to satisfy @c Tape + bool is_at_end(); - private: - FileHolder file_; - Pulse virtual_get_next_pulse(); - void virtual_reset(); +private: + FileHolder file_; + Pulse virtual_get_next_pulse(); + void virtual_reset(); - uint16_t load_address_; - uint16_t length_; + uint16_t load_address_; + uint16_t length_; - enum FilePhase { - FilePhaseLeadIn, - FilePhaseHeader, - FilePhaseHeaderDataGap, - FilePhaseData, - FilePhaseAtEnd - } file_phase_ = FilePhaseLeadIn; - int phase_offset_ = 0; + enum FilePhase { + FilePhaseLeadIn, + FilePhaseHeader, + FilePhaseHeaderDataGap, + FilePhaseData, + FilePhaseAtEnd + } file_phase_ = FilePhaseLeadIn; + int phase_offset_ = 0; - int bit_phase_ = 3; - enum OutputToken { - Leader, - Zero, - One, - WordMarker, - EndOfBlock, - Silence - } output_token_; - void get_next_output_token(); - uint8_t output_byte_; - uint8_t check_digit_; - uint8_t copy_mask_ = 0x80; + int bit_phase_ = 3; + enum OutputToken { + Leader, + Zero, + One, + WordMarker, + EndOfBlock, + Silence + } output_token_; + void get_next_output_token(); + uint8_t output_byte_; + uint8_t check_digit_; + uint8_t copy_mask_ = 0x80; }; } diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index 89e5e703d..6122a688a 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -22,53 +22,53 @@ namespace Storage::Tape { Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses. */ class UEF : public PulseQueuedTape, public TargetPlatform::TypeDistinguisher { - public: - /*! - Constructs a @c UEF containing content from the file with name @c file_name. +public: + /*! + Constructs a @c UEF containing content from the file with name @c file_name. - @throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF. - */ - UEF(const std::string &file_name); - ~UEF(); + @throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF. + */ + UEF(const std::string &file_name); + ~UEF(); - enum { - ErrorNotUEF - }; + enum { + ErrorNotUEF + }; - private: - void virtual_reset(); +private: + void virtual_reset(); - void set_platform_type(); - TargetPlatform::Type target_platform_type(); - TargetPlatform::Type platform_type_ = TargetPlatform::Acorn; + void set_platform_type(); + TargetPlatform::Type target_platform_type(); + TargetPlatform::Type platform_type_ = TargetPlatform::Acorn; - gzFile file_; - unsigned int time_base_ = 1200; - bool is_300_baud_ = false; + gzFile file_; + unsigned int time_base_ = 1200; + bool is_300_baud_ = false; - struct Chunk { - uint16_t id; - uint32_t length; - z_off_t start_of_next_chunk; - }; + struct Chunk { + uint16_t id; + uint32_t length; + z_off_t start_of_next_chunk; + }; - bool get_next_chunk(Chunk &); - void get_next_pulses(); + bool get_next_chunk(Chunk &); + void get_next_pulses(); - void queue_implicit_bit_pattern(uint32_t length); - void queue_explicit_bit_pattern(uint32_t length); + void queue_implicit_bit_pattern(uint32_t length); + void queue_explicit_bit_pattern(uint32_t length); - void queue_integer_gap(); - void queue_floating_point_gap(); + void queue_integer_gap(); + void queue_floating_point_gap(); - void queue_carrier_tone(); - void queue_carrier_tone_with_dummy(); + void queue_carrier_tone(); + void queue_carrier_tone_with_dummy(); - void queue_security_cycles(); - void queue_defined_data(uint32_t length); + void queue_security_cycles(); + void queue_defined_data(uint32_t length); - void queue_bit(int bit); - void queue_implicit_byte(uint8_t byte); + void queue_bit(int bit); + void queue_implicit_byte(uint8_t byte); }; } diff --git a/Storage/Tape/Formats/ZX80O81P.hpp b/Storage/Tape/Formats/ZX80O81P.hpp index 7935a3d14..17c7d085f 100644 --- a/Storage/Tape/Formats/ZX80O81P.hpp +++ b/Storage/Tape/Formats/ZX80O81P.hpp @@ -23,38 +23,38 @@ namespace Storage::Tape { Provides a @c Tape containing a ZX80-format .O tape image, which is a byte stream capture. */ class ZX80O81P: public Tape, public TargetPlatform::TypeDistinguisher { - public: - /*! - Constructs a @c ZX80O containing content from the file with name @c file_name. +public: + /*! + Constructs a @c ZX80O containing content from the file with name @c file_name. - @throws ErrorNotZX80O81P if this file could not be opened and recognised as a valid ZX80-format .O. - */ - ZX80O81P(const std::string &file_name); + @throws ErrorNotZX80O81P if this file could not be opened and recognised as a valid ZX80-format .O. + */ + ZX80O81P(const std::string &file_name); - enum { - ErrorNotZX80O81P - }; + enum { + ErrorNotZX80O81P + }; - private: - // implemented to satisfy @c Tape - bool is_at_end(); +private: + // implemented to satisfy @c Tape + bool is_at_end(); - // implemented to satisfy TargetPlatform::TypeDistinguisher - TargetPlatform::Type target_platform_type(); - TargetPlatform::Type platform_type_; + // implemented to satisfy TargetPlatform::TypeDistinguisher + TargetPlatform::Type target_platform_type(); + TargetPlatform::Type platform_type_; - void virtual_reset(); - Pulse virtual_get_next_pulse(); - bool has_finished_data(); + void virtual_reset(); + Pulse virtual_get_next_pulse(); + bool has_finished_data(); - uint8_t byte_; - int bit_pointer_; - int wave_pointer_; - bool is_past_silence_, has_ended_final_byte_; - bool is_high_; + uint8_t byte_; + int bit_pointer_; + int wave_pointer_; + bool is_past_silence_, has_ended_final_byte_; + bool is_high_; - std::vector data_; - std::size_t data_pointer_; + std::vector data_; + std::size_t data_pointer_; }; } diff --git a/Storage/Tape/Formats/ZXSpectrumTAP.hpp b/Storage/Tape/Formats/ZXSpectrumTAP.hpp index 50ddfc1fd..df7fb093a 100644 --- a/Storage/Tape/Formats/ZXSpectrumTAP.hpp +++ b/Storage/Tape/Formats/ZXSpectrumTAP.hpp @@ -21,36 +21,36 @@ namespace Storage::Tape { header and data blocks. */ class ZXSpectrumTAP: public Tape { - public: - /*! - Constructs a @c ZXSpectrumTAP containing content from the file with name @c file_name. +public: + /*! + Constructs a @c ZXSpectrumTAP containing content from the file with name @c file_name. - @throws ErrorNotZXSpectrumTAP if this file could not be opened and recognised as a valid Spectrum-format TAP. - */ - ZXSpectrumTAP(const std::string &file_name); + @throws ErrorNotZXSpectrumTAP if this file could not be opened and recognised as a valid Spectrum-format TAP. + */ + ZXSpectrumTAP(const std::string &file_name); - enum { - ErrorNotZXSpectrumTAP - }; + enum { + ErrorNotZXSpectrumTAP + }; - private: - Storage::FileHolder file_; +private: + Storage::FileHolder file_; - uint16_t block_length_ = 0; - uint8_t block_type_ = 0; - uint8_t data_byte_ = 0; - enum Phase { - PilotTone, - Data, - Gap - } phase_ = Phase::PilotTone; - int distance_into_phase_ = 0; - void read_next_block(); + uint16_t block_length_ = 0; + uint8_t block_type_ = 0; + uint8_t data_byte_ = 0; + enum Phase { + PilotTone, + Data, + Gap + } phase_ = Phase::PilotTone; + int distance_into_phase_ = 0; + void read_next_block(); - // Implemented to satisfy @c Tape. - bool is_at_end() override; - void virtual_reset() override; - Pulse virtual_get_next_pulse() override; + // Implemented to satisfy @c Tape. + bool is_at_end() override; + void virtual_reset() override; + Pulse virtual_get_next_pulse() override; }; } diff --git a/Storage/Tape/Parsers/Acorn.hpp b/Storage/Tape/Parsers/Acorn.hpp index 1e33d831c..c8cdb4357 100644 --- a/Storage/Tape/Parsers/Acorn.hpp +++ b/Storage/Tape/Parsers/Acorn.hpp @@ -15,28 +15,27 @@ namespace Storage::Tape::Acorn { class Shifter { - public: - Shifter(); +public: + Shifter(); - void process_pulse(const Storage::Tape::Tape::Pulse &pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &); - class Delegate { - public: - virtual void acorn_shifter_output_bit(int value) = 0; - }; - void set_delegate(Delegate *delegate) { - delegate_ = delegate; - } + struct Delegate { + virtual void acorn_shifter_output_bit(int value) = 0; + }; + void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } - void digital_phase_locked_loop_output_bit(int value); + void digital_phase_locked_loop_output_bit(int value); - private: - Storage::DigitalPhaseLockedLoop pll_; - bool was_high_; +private: + Storage::DigitalPhaseLockedLoop pll_; + bool was_high_; - unsigned int input_pattern_; + unsigned int input_pattern_; - Delegate *delegate_; + Delegate *delegate_; }; enum class SymbolType { @@ -44,23 +43,23 @@ enum class SymbolType { }; class Parser: public Storage::Tape::Parser, public Shifter::Delegate { - public: - Parser(); +public: + Parser(); - int get_next_bit(const std::shared_ptr &tape); - int get_next_byte(const std::shared_ptr &tape); - unsigned int get_next_short(const std::shared_ptr &tape); - unsigned int get_next_word(const std::shared_ptr &tape); - void reset_crc(); - uint16_t get_crc() const; + int get_next_bit(const std::shared_ptr &); + int get_next_byte(const std::shared_ptr &); + unsigned int get_next_short(const std::shared_ptr &); + unsigned int get_next_word(const std::shared_ptr &); + void reset_crc(); + uint16_t get_crc() const; - private: - void acorn_shifter_output_bit(int value) override; - void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; +private: + void acorn_shifter_output_bit(int value) override; + void process_pulse(const Storage::Tape::Tape::Pulse &) override; - bool did_update_shifter(int new_value, int length); - CRC::Generator crc_; - Shifter shifter_; + bool did_update_shifter(int new_value, int length); + CRC::Generator crc_; + Shifter shifter_; }; } diff --git a/Storage/Tape/Parsers/Commodore.hpp b/Storage/Tape/Parsers/Commodore.hpp index b5975502b..97748d70a 100644 --- a/Storage/Tape/Parsers/Commodore.hpp +++ b/Storage/Tape/Parsers/Commodore.hpp @@ -54,78 +54,78 @@ struct Data { }; class Parser: public Storage::Tape::PulseClassificationParser { - public: - Parser(); +public: + Parser(); - /*! - Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. - Returns @c nullptr if any wave-encoding level errors are encountered. - */ - std::unique_ptr
get_next_header(const std::shared_ptr &tape); + /*! + Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. + */ + std::unique_ptr
get_next_header(const std::shared_ptr &tape); - /*! - Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it. - Returns @c nullptr if any wave-encoding level errors are encountered. - */ - std::unique_ptr get_next_data(const std::shared_ptr &tape); + /*! + Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. + */ + std::unique_ptr get_next_data(const std::shared_ptr &tape); - private: - /*! - Template for the logic in selecting which of two copies of something to consider authoritative, - including setting the duplicate_matched flag. - */ - template - std::unique_ptr duplicate_match(std::unique_ptr first_copy, std::unique_ptr second_copy); +private: + /*! + Template for the logic in selecting which of two copies of something to consider authoritative, + including setting the duplicate_matched flag. + */ + template + std::unique_ptr duplicate_match(std::unique_ptr first_copy, std::unique_ptr second_copy); - std::unique_ptr
get_next_header_body(const std::shared_ptr &tape, bool is_original); - std::unique_ptr get_next_data_body(const std::shared_ptr &tape, bool is_original); + std::unique_ptr
get_next_header_body(const std::shared_ptr &tape, bool is_original); + std::unique_ptr get_next_data_body(const std::shared_ptr &tape, bool is_original); - /*! - Finds and completes the next landing zone. - */ - void proceed_to_landing_zone(const std::shared_ptr &tape, bool is_original); + /*! + Finds and completes the next landing zone. + */ + void proceed_to_landing_zone(const std::shared_ptr &tape, bool is_original); - /*! - Swallows the next byte; sets the error flag if it is not equal to @c value. - */ - void expect_byte(const std::shared_ptr &tape, uint8_t value); + /*! + Swallows the next byte; sets the error flag if it is not equal to @c value. + */ + void expect_byte(const std::shared_ptr &tape, uint8_t value); - uint8_t parity_byte_ = 0; - void reset_parity_byte(); - uint8_t get_parity_byte(); - void add_parity_byte(uint8_t byte); + uint8_t parity_byte_ = 0; + void reset_parity_byte(); + uint8_t get_parity_byte(); + void add_parity_byte(uint8_t byte); - /*! - Proceeds to the next word marker then returns the result of @c get_next_byte_contents. - */ - uint8_t get_next_byte(const std::shared_ptr &tape); + /*! + Proceeds to the next word marker then returns the result of @c get_next_byte_contents. + */ + uint8_t get_next_byte(const std::shared_ptr &tape); - /*! - Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One. - Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not - ::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight. - */ - uint8_t get_next_byte_contents(const std::shared_ptr &tape); + /*! + Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One. + Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not + ::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight. + */ + uint8_t get_next_byte_contents(const std::shared_ptr &tape); - /*! - Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format. - */ - uint16_t get_next_short(const std::shared_ptr &tape); + /*! + Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format. + */ + uint16_t get_next_short(const std::shared_ptr &tape); - /*! - Per the contract with Analyser::Static::TapeParser; sums time across pulses. If this pulse - indicates a high to low transition, inspects the time since the last transition, to produce - a long, medium, short or unrecognised wave period. - */ - void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; - bool previous_was_high_ = false; - float wave_period_ = 0.0f; + /*! + Per the contract with Analyser::Static::TapeParser; sums time across pulses. If this pulse + indicates a high to low transition, inspects the time since the last transition, to produce + a long, medium, short or unrecognised wave period. + */ + void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; + bool previous_was_high_ = false; + float wave_period_ = 0.0f; - /*! - Per the contract with Analyser::Static::TapeParser; produces any of a word marker, an end-of-block marker, - a zero, a one or a lead-in symbol based on the currently captured waves. - */ - void inspect_waves(const std::vector &waves) override; + /*! + Per the contract with Analyser::Static::TapeParser; produces any of a word marker, an end-of-block marker, + a zero, a one or a lead-in symbol based on the currently captured waves. + */ + void inspect_waves(const std::vector &waves) override; }; } diff --git a/Storage/Tape/Parsers/MSX.hpp b/Storage/Tape/Parsers/MSX.hpp index 29bf9b74a..ed95856da 100644 --- a/Storage/Tape/Parsers/MSX.hpp +++ b/Storage/Tape/Parsers/MSX.hpp @@ -16,34 +16,34 @@ namespace Storage::Tape::MSX { class Parser { - public: - struct FileSpeed { - uint8_t minimum_start_bit_duration; // i.e. LOWLIM - uint8_t low_high_disrimination_duration; // i.e. WINWID - }; +public: + struct FileSpeed { + uint8_t minimum_start_bit_duration; // i.e. LOWLIM + uint8_t low_high_disrimination_duration; // i.e. WINWID + }; - /*! - Finds the next header from the tape, determining constants for the - speed of file expected ahead. + /*! + Finds the next header from the tape, determining constants for the + speed of file expected ahead. - Attempts exactly to duplicate the MSX's TAPION function. + Attempts exactly to duplicate the MSX's TAPION function. - @param tape_player The tape player containing the tape to search. - @returns An instance of FileSpeed if a header is found before the end of the tape; - @c nullptr otherwise. - */ - static std::unique_ptr find_header(Storage::Tape::BinaryTapePlayer &tape_player); + @param tape_player The tape player containing the tape to search. + @returns An instance of FileSpeed if a header is found before the end of the tape; + @c nullptr otherwise. + */ + static std::unique_ptr find_header(Storage::Tape::BinaryTapePlayer &tape_player); - /*! - Attempts to read the next byte from the cassette, with data encoded - at the rate as defined by @c speed. + /*! + Attempts to read the next byte from the cassette, with data encoded + at the rate as defined by @c speed. - Attempts exactly to duplicate the MSX's TAPIN function. + Attempts exactly to duplicate the MSX's TAPIN function. - @returns A value in the range 0-255 if a byte is found before the end of the tape; - -1 otherwise. - */ - static int get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &tape_player); + @returns A value in the range 0-255 if a byte is found before the end of the tape; + -1 otherwise. + */ + static int get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &tape_player); }; } diff --git a/Storage/Tape/Parsers/Oric.hpp b/Storage/Tape/Parsers/Oric.hpp index 21a9be7fd..ca3487b17 100644 --- a/Storage/Tape/Parsers/Oric.hpp +++ b/Storage/Tape/Parsers/Oric.hpp @@ -24,29 +24,29 @@ enum class SymbolType { }; class Parser: public Storage::Tape::PulseClassificationParser { - public: - int get_next_byte(const std::shared_ptr &tape, bool use_fast_encoding); - bool sync_and_get_encoding_speed(const std::shared_ptr &tape); +public: + int get_next_byte(const std::shared_ptr &tape, bool use_fast_encoding); + bool sync_and_get_encoding_speed(const std::shared_ptr &tape); - private: - void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; - void inspect_waves(const std::vector &waves) override; +private: + void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; + void inspect_waves(const std::vector &waves) override; - enum DetectionMode { - FastData, - SlowData, - FastZero, - SlowZero, - Sync - } detection_mode_; - bool wave_was_high_; - float cycle_length_; + enum DetectionMode { + FastData, + SlowData, + FastZero, + SlowZero, + Sync + } detection_mode_; + bool wave_was_high_; + float cycle_length_; - struct Pattern { - WaveType type; - int count = 0; - }; - std::size_t pattern_matching_depth(const std::vector &waves, const Pattern *pattern); + struct Pattern { + WaveType type; + int count = 0; + }; + std::size_t pattern_matching_depth(const std::vector &waves, const Pattern *pattern); }; } diff --git a/Storage/Tape/Parsers/Spectrum.hpp b/Storage/Tape/Parsers/Spectrum.hpp index 4cb950160..d6db5ea5b 100644 --- a/Storage/Tape/Parsers/Spectrum.hpp +++ b/Storage/Tape/Parsers/Spectrum.hpp @@ -51,81 +51,81 @@ struct Block { }; class Parser: public Storage::Tape::PulseClassificationParser { - public: - enum class MachineType { - ZXSpectrum, - Enterprise, - SAMCoupe, - AmstradCPC - }; - Parser(MachineType); +public: + enum class MachineType { + ZXSpectrum, + Enterprise, + SAMCoupe, + AmstradCPC + }; + Parser(MachineType); - /*! - Calibrates the expected data speed using a value in the CPC's native tape-speed measurement scale. - */ - void set_cpc_read_speed(uint8_t); + /*! + Calibrates the expected data speed using a value in the CPC's native tape-speed measurement scale. + */ + void set_cpc_read_speed(uint8_t); - /*! - Finds the next block from the tape, if any. + /*! + Finds the next block from the tape, if any. - Following this call the tape will be positioned immediately after the byte that indicated the block type — - in Spectrum-world this seems to be called the flag byte. This call can therefore be followed up with one - of the get_ methods. - */ - std::optional find_block(const std::shared_ptr &tape); + Following this call the tape will be positioned immediately after the byte that indicated the block type — + in Spectrum-world this seems to be called the flag byte. This call can therefore be followed up with one + of the get_ methods. + */ + std::optional find_block(const std::shared_ptr &tape); - /*! - Reads the contents of the rest of this block, until the next gap. - */ - std::vector get_block_body(const std::shared_ptr &tape); + /*! + Reads the contents of the rest of this block, until the next gap. + */ + std::vector get_block_body(const std::shared_ptr &tape); - /*! - Reads a single byte from the tape, if there is one left, updating the internal checksum. + /*! + Reads a single byte from the tape, if there is one left, updating the internal checksum. - The checksum is computed as an exclusive OR of all bytes read. - */ - std::optional get_byte(const std::shared_ptr &tape); + The checksum is computed as an exclusive OR of all bytes read. + */ + std::optional get_byte(const std::shared_ptr &tape); - /*! - Seeds the internal checksum. - */ - void seed_checksum(uint8_t value = 0x00); + /*! + Seeds the internal checksum. + */ + void seed_checksum(uint8_t value = 0x00); - /*! - Push a pulse; primarily provided for Storage::Tape::PulseClassificationParser but also potentially useful - for picking up fast loading from an ongoing tape. - */ - void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; + /*! + Push a pulse; primarily provided for Storage::Tape::PulseClassificationParser but also potentially useful + for picking up fast loading from an ongoing tape. + */ + void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; - private: - const MachineType machine_type_; - constexpr bool should_flip_bytes() { - return machine_type_ == MachineType::Enterprise; - } - constexpr bool should_detect_speed() { - return machine_type_ != MachineType::ZXSpectrum; - } +private: + const MachineType machine_type_; + constexpr bool should_flip_bytes() { + return machine_type_ == MachineType::Enterprise; + } + constexpr bool should_detect_speed() { + return machine_type_ != MachineType::ZXSpectrum; + } - void inspect_waves(const std::vector &waves) override; + void inspect_waves(const std::vector &waves) override; - uint8_t checksum_ = 0; + uint8_t checksum_ = 0; - enum class SpeedDetectionPhase { - WaitingForGap, - WaitingForPilot, - CalibratingPilot, - Done - } speed_phase_ = SpeedDetectionPhase::Done; + enum class SpeedDetectionPhase { + WaitingForGap, + WaitingForPilot, + CalibratingPilot, + Done + } speed_phase_ = SpeedDetectionPhase::Done; - float too_long_ = 2600.0f; - float too_short_ = 600.0f; - float is_pilot_ = 1939.0f; - float is_one_ = 1282.0f; + float too_long_ = 2600.0f; + float too_short_ = 600.0f; + float is_pilot_ = 1939.0f; + float is_one_ = 1282.0f; - std::array calibration_pulses_; - size_t calibration_pulse_pointer_ = 0; + std::array calibration_pulses_; + size_t calibration_pulse_pointer_ = 0; - void set_cpc_one_zero_boundary(float); + void set_cpc_one_zero_boundary(float); }; } diff --git a/Storage/Tape/Parsers/TapeParser.hpp b/Storage/Tape/Parsers/TapeParser.hpp index c3964e270..6d62dee8d 100644 --- a/Storage/Tape/Parsers/TapeParser.hpp +++ b/Storage/Tape/Parsers/TapeParser.hpp @@ -17,81 +17,81 @@ namespace Storage::Tape { template class Parser { - public: - /// Resets the error flag. - void reset_error_flag() { error_flag_ = false; } - /// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise. - bool get_error_flag() { return error_flag_; } +public: + /// Resets the error flag. + void reset_error_flag() { error_flag_ = false; } + /// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise. + bool get_error_flag() { return error_flag_; } - /*! - Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol - or the tape runs out, returning the most-recently declared symbol. - */ - SymbolType get_next_symbol(const std::shared_ptr &tape) { - while(!has_next_symbol_ && !tape->is_at_end()) { - process_pulse(tape->get_next_pulse()); - } - if(!has_next_symbol_ && tape->is_at_end()) mark_end(); - has_next_symbol_ = false; - return next_symbol_; + /*! + Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol + or the tape runs out, returning the most-recently declared symbol. + */ + SymbolType get_next_symbol(const std::shared_ptr &tape) { + while(!has_next_symbol_ && !tape->is_at_end()) { + process_pulse(tape->get_next_pulse()); } + if(!has_next_symbol_ && tape->is_at_end()) mark_end(); + has_next_symbol_ = false; + return next_symbol_; + } - /*! - This class provides a single token of lookahead; return_symbol allows the single previous - token supplied by get_next_symbol to be returned, in which case it will be the thing returned - by the next call to get_next_symbol. - */ - void return_symbol(SymbolType symbol) { - assert(!has_next_symbol_); - has_next_symbol_ = true; - next_symbol_ = symbol; + /*! + This class provides a single token of lookahead; return_symbol allows the single previous + token supplied by get_next_symbol to be returned, in which case it will be the thing returned + by the next call to get_next_symbol. + */ + void return_symbol(SymbolType symbol) { + assert(!has_next_symbol_); + has_next_symbol_ = true; + next_symbol_ = symbol; + } + + /*! + @returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise. + */ + bool is_at_end(const std::shared_ptr &tape) { + return tape->is_at_end() && !has_next_symbol_; + } + + /*! + Swallows symbols until it reaches the first instance of the required symbol, swallows that + and returns. + */ + void proceed_to_symbol(const std::shared_ptr &tape, SymbolType required_symbol) { + while(!is_at_end(tape)) { + const SymbolType symbol = get_next_symbol(tape); + if(symbol == required_symbol) return; } + } - /*! - @returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise. - */ - bool is_at_end(const std::shared_ptr &tape) { - return tape->is_at_end() && !has_next_symbol_; - } +protected: + /*! + Should be implemented by subclasses. Consumes @c pulse. + */ + virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; - /*! - Swallows symbols until it reaches the first instance of the required symbol, swallows that - and returns. - */ - void proceed_to_symbol(const std::shared_ptr &tape, SymbolType required_symbol) { - while(!is_at_end(tape)) { - const SymbolType symbol = get_next_symbol(tape); - if(symbol == required_symbol) return; - } - } + /*! + An optional implementation for subclasses; called to announce that the tape has ended: that + no more process_pulse calls will occur. + */ + virtual void mark_end() {} - protected: - /*! - Should be implemented by subclasses. Consumes @c pulse. - */ - virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; + /*! + Sets @c symbol as the newly-recognised symbol. + */ + void push_symbol(SymbolType symbol) { + has_next_symbol_ = true; + next_symbol_ = symbol; + } - /*! - An optional implementation for subclasses; called to announce that the tape has ended: that - no more process_pulse calls will occur. - */ - virtual void mark_end() {} + void set_error_flag() { + error_flag_ = true; + } - /*! - Sets @c symbol as the newly-recognised symbol. - */ - void push_symbol(SymbolType symbol) { - has_next_symbol_ = true; - next_symbol_ = symbol; - } - - void set_error_flag() { - error_flag_ = true; - } - - bool error_flag_ = false; - SymbolType next_symbol_; - bool has_next_symbol_ = false; + bool error_flag_ = false; + SymbolType next_symbol_; + bool has_next_symbol_ = false; }; /*! @@ -103,55 +103,55 @@ template class Parser { the PLLParser. */ template class PulseClassificationParser: public Parser { - public: - virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; +public: + virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; - /* - process_pulse should either call @c push_wave or to take no action. - */ + /* + process_pulse should either call @c push_wave or to take no action. + */ - protected: - /*! - Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list. +protected: + /*! + Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list. - Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves - waves together represent @c symbol. - */ - void push_symbol(SymbolType symbol, int number_of_waves) { - Parser::push_symbol(symbol); - remove_waves(number_of_waves); - } + Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves + waves together represent @c symbol. + */ + void push_symbol(SymbolType symbol, int number_of_waves) { + Parser::push_symbol(symbol); + remove_waves(number_of_waves); + } - /*! - Adds @c wave to the back of the list of recognised waves and calls @c inspect_waves to check for a new symbol. + /*! + Adds @c wave to the back of the list of recognised waves and calls @c inspect_waves to check for a new symbol. - Expected to be called by subclasses from @c process_pulse as and when recognised waves arise. - */ - void push_wave(WaveType wave) { - wave_queue_.push_back(wave); - inspect_waves(wave_queue_); - } + Expected to be called by subclasses from @c process_pulse as and when recognised waves arise. + */ + void push_wave(WaveType wave) { + wave_queue_.push_back(wave); + inspect_waves(wave_queue_); + } - /*! - Removes @c nunber_of_waves waves from the front of the list. + /*! + Removes @c nunber_of_waves waves from the front of the list. - Expected to be called by subclasses from @c process_pulse if it is recognised that the first set of waves - do not form a valid symbol. - */ - void remove_waves(int number_of_waves) { - wave_queue_.erase(wave_queue_.begin(), wave_queue_.begin()+number_of_waves); - } + Expected to be called by subclasses from @c process_pulse if it is recognised that the first set of waves + do not form a valid symbol. + */ + void remove_waves(int number_of_waves) { + wave_queue_.erase(wave_queue_.begin(), wave_queue_.begin()+number_of_waves); + } - private: - /*! - Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is - found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries - removed from the start of @c waves that cannot form a valid symbol. Need not do anything while - the waves at the start of @c waves may end up forming a symbol but the symbol is not yet complete. - */ - virtual void inspect_waves(const std::vector &waves) = 0; +private: + /*! + Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is + found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries + removed from the start of @c waves that cannot form a valid symbol. Need not do anything while + the waves at the start of @c waves may end up forming a symbol but the symbol is not yet complete. + */ + virtual void inspect_waves(const std::vector &waves) = 0; - std::vector wave_queue_; + std::vector wave_queue_; }; } diff --git a/Storage/Tape/Parsers/ZX8081.hpp b/Storage/Tape/Parsers/ZX8081.hpp index 0e8ca6d6d..f235a6875 100644 --- a/Storage/Tape/Parsers/ZX8081.hpp +++ b/Storage/Tape/Parsers/ZX8081.hpp @@ -27,31 +27,31 @@ enum class SymbolType { }; class Parser: public Storage::Tape::PulseClassificationParser { - public: - Parser(); +public: + Parser(); - /*! - Reads and combines the next eight bits. Returns -1 if any errors are encountered. - */ - int get_next_byte(const std::shared_ptr &tape); + /*! + Reads and combines the next eight bits. Returns -1 if any errors are encountered. + */ + int get_next_byte(const std::shared_ptr &); - /*! - Waits for a long gap, reads all the bytes between that and the next long gap, then - attempts to parse those as a valid ZX80 or ZX81 file. If no file is found, - returns nullptr. - */ - std::shared_ptr get_next_file(const std::shared_ptr &tape); + /*! + Waits for a long gap, reads all the bytes between that and the next long gap, then + attempts to parse those as a valid ZX80 or ZX81 file. If no file is found, + returns nullptr. + */ + std::shared_ptr get_next_file(const std::shared_ptr &); - private: - bool pulse_was_high_; - Time pulse_time_; - void post_pulse(); +private: + bool pulse_was_high_; + Time pulse_time_; + void post_pulse(); - void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; - void mark_end() override; - void inspect_waves(const std::vector &waves) override; + void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; + void mark_end() override; + void inspect_waves(const std::vector &waves) override; - std::shared_ptr> get_next_file_data(const std::shared_ptr &tape); + std::shared_ptr> get_next_file_data(const std::shared_ptr &); }; } diff --git a/Storage/Tape/PulseQueuedTape.hpp b/Storage/Tape/PulseQueuedTape.hpp index 6b427d181..584814fb5 100644 --- a/Storage/Tape/PulseQueuedTape.hpp +++ b/Storage/Tape/PulseQueuedTape.hpp @@ -24,26 +24,26 @@ namespace Storage::Tape { virtual, giving subclasses a chance to provide the next batch of pulses. */ class PulseQueuedTape: public Tape { - public: - PulseQueuedTape(); - bool is_at_end(); +public: + PulseQueuedTape(); + bool is_at_end(); - protected: - void emplace_back(Tape::Pulse::Type type, Time length); - void emplace_back(const Tape::Pulse &&pulse); - void clear(); - bool empty(); +protected: + void emplace_back(Tape::Pulse::Type type, Time length); + void emplace_back(const Tape::Pulse &&pulse); + void clear(); + bool empty(); - void set_is_at_end(bool); - virtual void get_next_pulses() = 0; + void set_is_at_end(bool); + virtual void get_next_pulses() = 0; - private: - Pulse virtual_get_next_pulse(); - Pulse silence(); +private: + Pulse virtual_get_next_pulse(); + Pulse silence(); - std::vector queued_pulses_; - std::size_t pulse_pointer_; - bool is_at_end_; + std::vector queued_pulses_; + std::size_t pulse_pointer_; + bool is_at_end_; }; } diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 43efb318d..d1a792d53 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -31,61 +31,61 @@ namespace Storage::Tape { a better implementation than a linear search from the @c reset time can be implemented. */ class Tape { - public: - struct Pulse { - enum Type { - High, Low, Zero - } type; - Time length; +public: + struct Pulse { + enum Type { + High, Low, Zero + } type; + Time length; - Pulse(Type type, Time length) : type(type), length(length) {} - Pulse() = default; - }; + Pulse(Type type, Time length) : type(type), length(length) {} + Pulse() = default; + }; - /*! - If at the start of the tape returns the first stored pulse. Otherwise advances past - the last-returned pulse and returns the next. + /*! + If at the start of the tape returns the first stored pulse. Otherwise advances past + the last-returned pulse and returns the next. - @returns the pulse that begins at the current cursor position. - */ - Pulse get_next_pulse(); + @returns the pulse that begins at the current cursor position. + */ + Pulse get_next_pulse(); - /// Returns the tape to the beginning. - void reset(); + /// Returns the tape to the beginning. + void reset(); - /// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise. - virtual bool is_at_end() = 0; + /// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise. + virtual bool is_at_end() = 0; - /*! - Returns a numerical representation of progression into the tape. Precision is arbitrary but - required to be at least to the whole pulse. Greater numbers are later than earlier numbers, - but not necessarily continuous. - */ - virtual uint64_t get_offset(); + /*! + Returns a numerical representation of progression into the tape. Precision is arbitrary but + required to be at least to the whole pulse. Greater numbers are later than earlier numbers, + but not necessarily continuous. + */ + virtual uint64_t get_offset(); - /*! - Moves the tape to the first time at which the specified offset would be returned by get_offset. - */ - virtual void set_offset(uint64_t); + /*! + Moves the tape to the first time at which the specified offset would be returned by get_offset. + */ + virtual void set_offset(uint64_t); - /*! - Calculates and returns the amount of time that has elapsed since the time began. Potentially expensive. - */ - virtual Time get_current_time(); + /*! + Calculates and returns the amount of time that has elapsed since the time began. Potentially expensive. + */ + virtual Time get_current_time(); - /*! - Seeks to @c time. Potentially expensive. - */ - virtual void seek(Time &time); + /*! + Seeks to @c time. Potentially expensive. + */ + virtual void seek(Time &time); - virtual ~Tape() = default; + virtual ~Tape() = default; - private: - uint64_t offset_; - Tape::Pulse pulse_; +private: + uint64_t offset_; + Tape::Pulse pulse_; - virtual Pulse virtual_get_next_pulse() = 0; - virtual void virtual_reset() = 0; + virtual Pulse virtual_get_next_pulse() = 0; + virtual void virtual_reset() = 0; }; /*! @@ -96,32 +96,32 @@ class Tape { can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type. */ class TapePlayer: public TimedEventLoop, public ClockingHint::Source { - public: - TapePlayer(int input_clock_rate); - virtual ~TapePlayer() = default; +public: + TapePlayer(int input_clock_rate); + virtual ~TapePlayer() = default; - void set_tape(std::shared_ptr tape); - bool has_tape(); - std::shared_ptr get_tape(); + void set_tape(std::shared_ptr tape); + bool has_tape(); + std::shared_ptr get_tape(); - void run_for(const Cycles cycles); + void run_for(const Cycles cycles); - void run_for_input_pulse(); + void run_for_input_pulse(); - ClockingHint::Preference preferred_clocking() const override; + ClockingHint::Preference preferred_clocking() const override; - Tape::Pulse get_current_pulse(); - void complete_pulse(); + Tape::Pulse get_current_pulse(); + void complete_pulse(); - protected: - virtual void process_next_event() override; - virtual void process_input_pulse(const Tape::Pulse &pulse) = 0; +protected: + virtual void process_next_event() override; + virtual void process_input_pulse(const Tape::Pulse &pulse) = 0; - private: - inline void get_next_pulse(); +private: + inline void get_next_pulse(); - std::shared_ptr tape_; - Tape::Pulse current_pulse_; + std::shared_ptr tape_; + Tape::Pulse current_pulse_; }; /*! @@ -133,33 +133,32 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source { They can also provide a delegate to be notified upon any change in the input level. */ class BinaryTapePlayer : public TapePlayer { - public: - BinaryTapePlayer(int input_clock_rate); - void set_motor_control(bool enabled); - bool get_motor_control() const; +public: + BinaryTapePlayer(int input_clock_rate); + void set_motor_control(bool enabled); + bool get_motor_control() const; - void set_tape_output(bool set); - bool get_input() const; + void set_tape_output(bool set); + bool get_input() const; - void run_for(const Cycles cycles); + void run_for(const Cycles cycles); - class Delegate { - public: - virtual void tape_did_change_input(BinaryTapePlayer *tape_player) = 0; - }; - void set_delegate(Delegate *delegate); + struct Delegate { + virtual void tape_did_change_input(BinaryTapePlayer *tape_player) = 0; + }; + void set_delegate(Delegate *delegate); - ClockingHint::Preference preferred_clocking() const final; + ClockingHint::Preference preferred_clocking() const final; - void set_activity_observer(Activity::Observer *observer); + void set_activity_observer(Activity::Observer *observer); - protected: - Delegate *delegate_ = nullptr; - void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) final; - bool input_level_ = false; - bool motor_is_running_ = false; +protected: + Delegate *delegate_ = nullptr; + void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) final; + bool input_level_ = false; + bool motor_is_running_ = false; - Activity::Observer *observer_ = nullptr; + Activity::Observer *observer_ = nullptr; }; } diff --git a/Storage/TargetPlatforms.hpp b/Storage/TargetPlatforms.hpp index 94ecb1473..32b784544 100644 --- a/Storage/TargetPlatforms.hpp +++ b/Storage/TargetPlatforms.hpp @@ -49,8 +49,8 @@ enum Type: IntType { }; class TypeDistinguisher { - public: - virtual Type target_platform_type() = 0; +public: + virtual Type target_platform_type() = 0; }; } diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index 5dd083161..97860fa97 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -16,93 +16,93 @@ namespace Storage { +/*! + Provides a mechanism for arbitrarily timed events to be processed according to a fixed-base + discrete clock signal, ensuring correct timing. + + Subclasses are responsible for calling @c set_next_event_time_interval to establish the time + until a next event; @c process_next_event will be called when that event occurs, with progression + determined via @c run_for. + + Due to the aggregation of total timing information between events, e.g. if an event loop has + a clock rate of 1000 ticks per second and a steady stream of events that occur 10,000 times a second, + bookkeeping is necessary to ensure that 10 events are triggered per tick. Subclasses should call + @c reset_timer if there is a discontinuity in events. + + Subclasses may also call @c jump_to_next_event to cause the next event to be communicated instantly. + + Subclasses are therefore expected to call @c set_next_event_time_interval upon obtaining an event stream, + and again in response to each call to @c process_next_event while events are ongoing. They may use + @c reset_timer to initiate a distinctly-timed stream or @c jump_to_next_event to short-circuit the timing + loop and fast forward immediately to the next event. +*/ +class TimedEventLoop { +public: /*! - Provides a mechanism for arbitrarily timed events to be processed according to a fixed-base - discrete clock signal, ensuring correct timing. - - Subclasses are responsible for calling @c set_next_event_time_interval to establish the time - until a next event; @c process_next_event will be called when that event occurs, with progression - determined via @c run_for. - - Due to the aggregation of total timing information between events, e.g. if an event loop has - a clock rate of 1000 ticks per second and a steady stream of events that occur 10,000 times a second, - bookkeeping is necessary to ensure that 10 events are triggered per tick. Subclasses should call - @c reset_timer if there is a discontinuity in events. - - Subclasses may also call @c jump_to_next_event to cause the next event to be communicated instantly. - - Subclasses are therefore expected to call @c set_next_event_time_interval upon obtaining an event stream, - and again in response to each call to @c process_next_event while events are ongoing. They may use - @c reset_timer to initiate a distinctly-timed stream or @c jump_to_next_event to short-circuit the timing - loop and fast forward immediately to the next event. + Constructs a timed event loop that will be clocked at @c input_clock_rate. */ - class TimedEventLoop { - public: - /*! - Constructs a timed event loop that will be clocked at @c input_clock_rate. - */ - TimedEventLoop(Cycles::IntType input_clock_rate); + TimedEventLoop(Cycles::IntType input_clock_rate); - /*! - Advances the event loop by @c number_of_cycles cycles. - */ - void run_for(const Cycles cycles); + /*! + Advances the event loop by @c number_of_cycles cycles. + */ + void run_for(const Cycles cycles); - /*! - @returns the number of whole cycles remaining until the next event is triggered. - */ - Cycles::IntType get_cycles_until_next_event() const; + /*! + @returns the number of whole cycles remaining until the next event is triggered. + */ + Cycles::IntType get_cycles_until_next_event() const; - /*! - @returns the input clock rate. - */ - Cycles::IntType get_input_clock_rate() const; + /*! + @returns the input clock rate. + */ + Cycles::IntType get_input_clock_rate() const; - protected: - /*! - Sets the time interval, as a proportion of a second, until the next event should be triggered. - */ - void set_next_event_time_interval(Time interval); - void set_next_event_time_interval(float interval); +protected: + /*! + Sets the time interval, as a proportion of a second, until the next event should be triggered. + */ + void set_next_event_time_interval(Time interval); + void set_next_event_time_interval(float interval); - /*! - Communicates that the next event is triggered. A subclass will idiomatically process that event - and make a fresh call to @c set_next_event_time_interval to keep the event loop running. - */ - virtual void process_next_event() = 0; + /*! + Communicates that the next event is triggered. A subclass will idiomatically process that event + and make a fresh call to @c set_next_event_time_interval to keep the event loop running. + */ + virtual void process_next_event() = 0; - /*! - Optionally allows a subclass to track time within run_for periods; if a subclass implements - advnace then it will receive advance increments that add up to the number of cycles supplied - to run_for, but calls to process_next_event will be precisely interspersed. No time will carry - forward between calls into run_for; a subclass can receive arbitrarily many instructions to - advance before receiving a process_next_event. - */ - virtual void advance([[maybe_unused]] const Cycles cycles) {}; + /*! + Optionally allows a subclass to track time within run_for periods; if a subclass implements + advnace then it will receive advance increments that add up to the number of cycles supplied + to run_for, but calls to process_next_event will be precisely interspersed. No time will carry + forward between calls into run_for; a subclass can receive arbitrarily many instructions to + advance before receiving a process_next_event. + */ + virtual void advance([[maybe_unused]] const Cycles cycles) {}; - /*! - Resets timing, throwing away any current internal state. So clears any fractional ticks - that the event loop is currently tracking. - */ - void reset_timer(); + /*! + Resets timing, throwing away any current internal state. So clears any fractional ticks + that the event loop is currently tracking. + */ + void reset_timer(); - /*! - Causes an immediate call to @c process_next_event and a call to @c reset_timer with the - net effect of processing the current event immediately and fast forwarding exactly to the - start of the interval prior to the next event. - */ - void jump_to_next_event(); + /*! + Causes an immediate call to @c process_next_event and a call to @c reset_timer with the + net effect of processing the current event immediately and fast forwarding exactly to the + start of the interval prior to the next event. + */ + void jump_to_next_event(); - /*! - @returns the amount of time that has passed since the last call to @c set_next_time_interval, - which will always be less than or equal to the time that was supplied to @c set_next_time_interval. - */ - Time get_time_into_next_event(); + /*! + @returns the amount of time that has passed since the last call to @c set_next_time_interval, + which will always be less than or equal to the time that was supplied to @c set_next_time_interval. + */ + Time get_time_into_next_event(); - private: - Cycles::IntType input_clock_rate_ = 0; - Cycles::IntType cycles_until_event_ = 0; - float subcycles_until_event_ = 0.0f; - }; +private: + Cycles::IntType input_clock_rate_ = 0; + Cycles::IntType cycles_until_event_ = 0; + float subcycles_until_event_ = 0.0f; +}; }